├── .clang-format ├── .git-blame-ignore-revs ├── .github ├── dependabot.yml └── workflows │ └── wheels.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── _static │ ├── nbsphinx-gallery.css │ └── theme_overrides.css ├── _templates │ └── page.html ├── conf.py ├── docs_api │ ├── conf.py │ └── list_api.rst ├── exts │ └── sphinxtr │ │ ├── LICENSE │ │ ├── custombackports.py │ │ ├── figtable.py │ │ ├── fix_equation_ref.py │ │ ├── html_mods.py │ │ ├── latex_mods.py │ │ ├── numfig.py │ │ ├── numsec.py │ │ ├── pluginparameters.py │ │ ├── singlehtml_toc.py │ │ ├── singletext.py │ │ └── subfig.py ├── generate_plugin_doc.py ├── generated │ ├── extracted_rst_api.rst │ └── mitsuba_api.rst ├── images │ ├── DO_NOT_PUT_FILES_HERE.txt │ ├── logo.png │ ├── logo_plain.pdf │ ├── logo_plain.png │ ├── logo_plain.svg │ └── mitsuba-logo-white-bg.png ├── index.rst ├── issue_template.md ├── porting_3_6.rst ├── pull_request_template.md ├── references.bib ├── release.rst ├── release_notes.rst ├── requirements.txt ├── resources ├── src │ ├── api_reference.rst │ ├── developer_guide.rst │ ├── developer_guide │ │ ├── compiling.rst │ │ ├── documentation.rst │ │ ├── testing.rst │ │ ├── variants_cpp.rst │ │ └── writing_plugin.rst │ ├── gallery.rst │ ├── generated │ ├── how_to_guides │ ├── how_to_guides.rst │ ├── inverse_rendering_tutorials.rst │ ├── key_topics.rst │ ├── key_topics │ │ ├── differences.rst │ │ ├── polarization.rst │ │ ├── scene_format.rst │ │ └── variants.rst │ ├── optix_setup.rst │ ├── others_tutorials.rst │ ├── plugin_reference.rst │ ├── plugin_reference │ │ ├── section_bsdfs.rst │ │ ├── section_emitters.rst │ │ ├── section_films.rst │ │ ├── section_integrators.rst │ │ ├── section_media.rst │ │ ├── section_phase.rst │ │ ├── section_rfilters.rst │ │ ├── section_samplers.rst │ │ ├── section_sensors.rst │ │ ├── section_shapes.rst │ │ ├── section_spectra.rst │ │ ├── section_textures.rst │ │ └── section_volumes.rst │ ├── quickstart │ └── rendering_tutorials.rst └── zz_bibliography.rst ├── ext ├── CMakeLists.txt └── rgb2spec │ ├── CMakeLists.txt │ ├── details │ ├── cie1931.h │ └── lu.h │ ├── rgb2spec.c │ ├── rgb2spec.h │ └── rgb2spec_opt.cpp ├── include └── mitsuba │ ├── core │ ├── appender.h │ ├── argparser.h │ ├── atomic.h │ ├── bbox.h │ ├── bitmap.h │ ├── bsphere.h │ ├── class.h │ ├── distr_1d.h │ ├── distr_2d.h │ ├── dstream.h │ ├── field.h │ ├── filesystem.h │ ├── formatter.h │ ├── frame.h │ ├── fresolver.h │ ├── fstream.h │ ├── fwd.h │ ├── hash.h │ ├── jit.h │ ├── logger.h │ ├── math.h │ ├── mmap.h │ ├── mstream.h │ ├── object.h │ ├── platform.h │ ├── plugin.h │ ├── profiler.h │ ├── progress.h │ ├── properties.h │ ├── qmc.h │ ├── quad.h │ ├── random.h │ ├── ray.h │ ├── rfilter.h │ ├── simd.h │ ├── spectrum.h │ ├── spline.h │ ├── stream.h │ ├── string.h │ ├── struct.h │ ├── tensor.h │ ├── thread.h │ ├── timer.h │ ├── traits.h │ ├── transform.h │ ├── util.h │ ├── variant.h │ ├── vector.h │ ├── warp.h │ ├── xml.h │ └── zstream.h │ ├── mitsuba.h │ ├── python │ ├── docstr.h │ └── python.h │ ├── render │ ├── bsdf.h │ ├── emitter.h │ ├── endpoint.h │ ├── film.h │ ├── fresnel.h │ ├── fwd.h │ ├── imageblock.h │ ├── integrator.h │ ├── interaction.h │ ├── ior.h │ ├── kdtree.h │ ├── medium.h │ ├── mesh.h │ ├── microfacet.h │ ├── microflake.h │ ├── mueller.h │ ├── optix │ │ ├── bbox.cuh │ │ ├── common.h │ │ ├── math.cuh │ │ ├── matrix.cuh │ │ ├── ray.cuh │ │ ├── shapes.h │ │ └── vector.cuh │ ├── optix_api.h │ ├── optixdenoiser.h │ ├── phase.h │ ├── records.h │ ├── sampler.h │ ├── scene.h │ ├── sensor.h │ ├── shape.h │ ├── shapegroup.h │ ├── spiral.h │ ├── srgb.h │ ├── sunsky.h │ ├── texture.h │ ├── volume.h │ └── volumegrid.h │ └── ui │ ├── fwd.h │ ├── texture.h │ └── viewer.h ├── pyproject.toml ├── resources ├── check-style.sh ├── configure.py ├── mitsuba-logo.png ├── mitsuba.conf.template ├── ptx │ ├── Makefile │ └── optix_rt.ptx ├── setpath.bat ├── setpath.ps1 ├── setpath.sh └── variant-stub.cmake ├── setup.py └── src ├── CMakeLists.txt ├── bsdfs ├── CMakeLists.txt ├── __init__.py ├── blendbsdf.cpp ├── bumpmap.cpp ├── circular.cpp ├── conductor.cpp ├── dielectric.cpp ├── diffuse.cpp ├── hair.cpp ├── mask.cpp ├── measured.cpp ├── measured_polarized.cpp ├── normalmap.cpp ├── null.cpp ├── plastic.cpp ├── polarizer.cpp ├── pplastic.cpp ├── principled.cpp ├── principledhelpers.h ├── principledthin.cpp ├── retarder.cpp ├── roughconductor.cpp ├── roughdielectric.cpp ├── roughplastic.cpp ├── tests │ ├── __init__.py │ ├── test_blendbsdf.py │ ├── test_conductor.py │ ├── test_dielectric.py │ ├── test_diffuse.py │ ├── test_hair.py │ ├── test_measured_polarized.py │ ├── test_normalmap.py │ ├── test_polarizer.py │ ├── test_pplastic.py │ ├── test_principled.py │ ├── test_principledthin.py │ ├── test_retarder.py │ ├── test_rough_conductor.py │ ├── test_rough_dielectric.py │ ├── test_rough_plastic.py │ └── test_twosided.py ├── thindielectric.cpp └── twosided.cpp ├── cmake └── FindSphinx.cmake ├── conftest.py ├── core ├── CMakeLists.txt ├── __init__.py ├── appender.cpp ├── argparser.cpp ├── bitmap.cpp ├── class.cpp ├── dither-matrix256.cpp ├── dstream.cpp ├── filesystem.cpp ├── formatter.cpp ├── fresolver.cpp ├── fstream.cpp ├── jit.cpp ├── logger.cpp ├── mmap.cpp ├── mstream.cpp ├── object.cpp ├── plugin.cpp ├── profiler.cpp ├── progress.cpp ├── properties.cpp ├── python │ ├── CMakeLists.txt │ ├── appender.cpp │ ├── argparser.cpp │ ├── atomic.cpp │ ├── bbox_v.cpp │ ├── bitmap.cpp │ ├── bsphere_v.cpp │ ├── cast.cpp │ ├── distr_1d_v.cpp │ ├── distr_2d_v.cpp │ ├── drjit_v.cpp │ ├── filesystem.cpp │ ├── formatter.cpp │ ├── frame_v.cpp │ ├── fresolver.cpp │ ├── logger.cpp │ ├── math_v.cpp │ ├── misc.cpp │ ├── mmap.cpp │ ├── object.cpp │ ├── object_v.cpp │ ├── progress.cpp │ ├── properties_v.cpp │ ├── qmc_v.cpp │ ├── quad_v.cpp │ ├── random_v.cpp │ ├── ray_v.cpp │ ├── rfilter.cpp │ ├── rfilter_v.cpp │ ├── spectrum_v.cpp │ ├── spline_v.cpp │ ├── stream.cpp │ ├── struct.cpp │ ├── thread.cpp │ ├── timer.cpp │ ├── transform_v.cpp │ ├── vector_v.cpp │ ├── warp_v.cpp │ └── xml_v.cpp ├── qmc.cpp ├── rfilter.cpp ├── spectrum.cpp ├── stream.cpp ├── string.cpp ├── struct.cpp ├── tensor.cpp ├── tests │ ├── __init__.py │ ├── test_argparser.py │ ├── test_atomic.py │ ├── test_bbox.py │ ├── test_bitmap.py │ ├── test_bsphere.py │ ├── test_dict.py │ ├── test_distr_1d.py │ ├── test_distr_2d.py │ ├── test_filesystem.py │ ├── test_frame.py │ ├── test_logger.py │ ├── test_math.py │ ├── test_mmap.py │ ├── test_properties.py │ ├── test_python.py │ ├── test_qmc.py │ ├── test_quad.py │ ├── test_random.py │ ├── test_spectrum.py │ ├── test_spline.py │ ├── test_stream.py │ ├── test_struct.py │ ├── test_thread.py │ ├── test_transform.py │ ├── test_util.py │ ├── test_variants.py │ ├── test_vector.py │ ├── test_warp.py │ ├── test_write_xml.py │ └── test_xml.py ├── thread.cpp ├── transform.cpp ├── util.cpp ├── xml.cpp └── zstream.cpp ├── emitters ├── CMakeLists.txt ├── __init__.py ├── area.cpp ├── constant.cpp ├── directional.cpp ├── directionalarea.cpp ├── envmap.cpp ├── point.cpp ├── projector.cpp ├── spot.cpp ├── sunsky.cpp └── tests │ ├── __init__.py │ ├── test_area.py │ ├── test_constant.py │ ├── test_directional.py │ ├── test_directionalarea.py │ ├── test_envmap.py │ ├── test_point.py │ ├── test_projector.py │ ├── test_spot.py │ └── test_sunsky.py ├── films ├── CMakeLists.txt ├── hdrfilm.cpp ├── specfilm.cpp └── tests │ ├── test_hdrfilm.py │ └── test_specfilm.py ├── integrators ├── CMakeLists.txt ├── __init__.py ├── aov.cpp ├── depth.cpp ├── direct.cpp ├── moment.cpp ├── path.cpp ├── ptracer.cpp ├── stokes.cpp ├── tests │ ├── __init__.py │ ├── test_ad_integrators.py │ ├── test_aov.py │ ├── test_integrators.py │ ├── test_ptracer.py │ └── test_volprim_rf_basic.py ├── volpath.cpp └── volpathmis.cpp ├── media ├── CMakeLists.txt ├── heterogeneous.cpp ├── homogeneous.cpp └── tests │ ├── __init__.py │ └── test_homogeneous.py ├── mitsuba ├── CMakeLists.txt └── mitsuba.cpp ├── phase ├── CMakeLists.txt ├── blendphase.cpp ├── hg.cpp ├── isotropic.cpp ├── rayleigh.cpp ├── sggx.cpp ├── tabphase.cpp └── tests │ ├── test_blendphase.py │ ├── test_hg.py │ ├── test_isotropic.py │ ├── test_rayleigh.py │ ├── test_sggx.py │ ├── test_tabphase.py │ └── test_trampoline.py ├── python ├── CMakeLists.txt ├── __init__.py ├── alias.cpp ├── main.cpp ├── main_v.cpp ├── mitsuba_stubs │ └── __init__.py ├── python │ ├── __init__.py │ ├── ad │ │ ├── __init__.py │ │ ├── guiding.py │ │ ├── integrators │ │ │ ├── __init__.py │ │ │ ├── common.py │ │ │ ├── direct_projective.py │ │ │ ├── prb.py │ │ │ ├── prb_basic.py │ │ │ ├── prb_projective.py │ │ │ ├── prbvolpath.py │ │ │ └── volprim_rf_basic.py │ │ ├── largesteps.py │ │ ├── optimizers.py │ │ └── projective.py │ ├── chi2.py │ ├── cli.py │ ├── math_py.py │ ├── polvis.py │ ├── sys_info.py │ ├── test │ │ ├── __init__.py │ │ └── util.py │ ├── testing.py │ ├── tonemap.py │ ├── util.py │ └── xml.py └── stubs.pat ├── python_tests ├── __init__.py ├── test_largesteps.py └── test_util.py ├── render ├── CMakeLists.txt ├── __init__.py ├── bsdf.cpp ├── emitter.cpp ├── endpoint.cpp ├── film.cpp ├── imageblock.cpp ├── integrator.cpp ├── kdtree.cpp ├── medium.cpp ├── mesh.cpp ├── microfacet.cpp ├── optix │ └── optix_rt.cu ├── optix_api.cpp ├── optixdenoiser.cpp ├── phase.cpp ├── python │ ├── CMakeLists.txt │ ├── bsdf.cpp │ ├── bsdf_v.cpp │ ├── emitter.cpp │ ├── emitter_v.cpp │ ├── endpoint_v.cpp │ ├── film.cpp │ ├── film_v.cpp │ ├── fresnel_v.cpp │ ├── imageblock_v.cpp │ ├── integrator_v.cpp │ ├── interaction.cpp │ ├── interaction_v.cpp │ ├── medium_v.cpp │ ├── microfacet.cpp │ ├── microfacet_v.cpp │ ├── microflake_v.cpp │ ├── mueller_v.cpp │ ├── optixdenoiser_v.cpp │ ├── phase.cpp │ ├── phase_v.cpp │ ├── records_v.cpp │ ├── sampler_v.cpp │ ├── scene_v.cpp │ ├── sensor.cpp │ ├── sensor_v.cpp │ ├── shape.cpp │ ├── shape_v.cpp │ ├── signal.h │ ├── spiral.cpp │ ├── srgb_v.cpp │ ├── texture_v.cpp │ ├── volume_v.cpp │ └── volumegrid_v.cpp ├── sampler.cpp ├── scene.cpp ├── scene_embree.inl ├── scene_native.inl ├── scene_optix.inl ├── sensor.cpp ├── shape.cpp ├── shapegroup.cpp ├── spiral.cpp ├── srgb.cpp ├── tests │ ├── __init__.py │ ├── test_ad.py │ ├── test_bsdf.py │ ├── test_dispatch.py │ ├── test_emitter.py │ ├── test_film.py │ ├── test_fresnel.py │ ├── test_imageblock.py │ ├── test_interaction.py │ ├── test_kdtrees.py │ ├── test_medium.py │ ├── test_megakernel.py │ ├── test_mesh.py │ ├── test_microfacet.py │ ├── test_microflake.py │ ├── test_mueller.py │ ├── test_optixdenoiser.py │ ├── test_phase.py │ ├── test_records.py │ ├── test_renders.py │ ├── test_scene.py │ ├── test_sensor.py │ ├── test_shape.py │ ├── test_spectra.py │ ├── test_spiral.py │ ├── test_tutorials.py │ └── test_volumegrid.py ├── texture.cpp ├── volume.cpp └── volumegrid.cpp ├── rfilters ├── CMakeLists.txt ├── __init__.py ├── box.cpp ├── catmullrom.cpp ├── gaussian.cpp ├── lanczos.cpp ├── mitchell.cpp ├── tent.cpp └── tests │ ├── __init__.py │ └── test_rfilter.py ├── samplers ├── CMakeLists.txt ├── __init__.py ├── independent.cpp ├── ldsampler.cpp ├── multijitter.cpp ├── orthogonal.cpp ├── stratified.cpp └── tests │ ├── __init__.py │ ├── test_independent.py │ ├── test_ldsampler.py │ ├── test_multijitter.py │ ├── test_orthogonal.py │ ├── test_stratified.py │ └── utils.py ├── sensors ├── CMakeLists.txt ├── __init__.py ├── batch.cpp ├── distant.cpp ├── irradiancemeter.cpp ├── orthographic.cpp ├── perspective.cpp ├── radiancemeter.cpp ├── tests │ ├── __init__.py │ ├── test_batch.py │ ├── test_distant.py │ ├── test_irradiancemeter.py │ ├── test_orthographic.py │ ├── test_perspective.py │ ├── test_radiancemeter.py │ └── test_thinlens.py └── thinlens.cpp ├── shapes ├── CMakeLists.txt ├── __init__.py ├── blender.cpp ├── bsplinecurve.cpp ├── cube.cpp ├── cylinder.cpp ├── disk.cpp ├── ellipsoids.cpp ├── ellipsoids.h ├── ellipsoidsmesh.cpp ├── instance.cpp ├── linearcurve.cpp ├── merge.cpp ├── obj.cpp ├── optix │ ├── cylinder.cuh │ ├── disk.cuh │ ├── ellipsoids.cuh │ ├── sdfgrid.cuh │ └── sphere.cuh ├── ply.cpp ├── ply.h ├── rectangle.cpp ├── sdfgrid.cpp ├── serialized.cpp ├── shapegroup.cpp ├── sphere.cpp └── tests │ ├── __init__.py │ ├── test_bsplinecurve.py │ ├── test_cube.py │ ├── test_cylinder.py │ ├── test_disk.py │ ├── test_instance.py │ ├── test_linearcurve.py │ ├── test_merge.py │ ├── test_rectangle.py │ ├── test_sdfgrid.py │ ├── test_shapegroup.py │ └── test_sphere.py ├── spectra ├── CMakeLists.txt ├── blackbody.cpp ├── d65.cpp ├── irregular.cpp ├── rawconstant.cpp ├── regular.cpp ├── srgb.cpp ├── tests │ ├── test_d65.py │ ├── test_irregular.py │ ├── test_rawconstant.py │ └── test_regular.py └── uniform.cpp ├── textures ├── CMakeLists.txt ├── bitmap.cpp ├── checkerboard.cpp ├── mesh_attribute.cpp ├── tests │ ├── test_bitmap.py │ ├── test_mesh_attribute.py │ └── test_volume.py └── volume.cpp └── volumes ├── CMakeLists.txt ├── const.cpp ├── grid.cpp └── tests ├── test_constant.py └── test_grid.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignConsecutiveAssignments: true 5 | AlignEscapedNewlinesLeft: false 6 | AlignTrailingComments: true 7 | AllowAllParametersOfDeclarationOnNextLine: true 8 | AllowShortIfStatementsOnASingleLine: false 9 | AllowShortLoopsOnASingleLine: false 10 | AlwaysBreakBeforeMultilineStrings: false 11 | AlwaysBreakTemplateDeclarations: false 12 | BinPackParameters: true 13 | BreakBeforeBinaryOperators: false 14 | BreakBeforeBraces: Attach 15 | BreakBeforeTernaryOperators: true 16 | BreakConstructorInitializersBeforeComma: false 17 | ColumnLimit: 80 18 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 19 | ConstructorInitializerIndentWidth: 4 20 | ContinuationIndentWidth: 4 21 | Cpp11BracedListStyle: false 22 | DerivePointerBinding: false 23 | ExperimentalAutoDetectBinPacking: false 24 | IndentCaseLabels: true 25 | IndentFunctionDeclarationAfterType: false 26 | IndentWidth: 4 27 | MaxEmptyLinesToKeep: 1 28 | NamespaceIndentation: None 29 | ObjCSpaceBeforeProtocolList: true 30 | PenaltyBreakBeforeFirstCallParameter: 19 31 | PenaltyBreakComment: 60 32 | PenaltyBreakFirstLessLess: 120 33 | PenaltyBreakString: 1000 34 | PenaltyExcessCharacter: 1000000 35 | PenaltyReturnTypeOnItsOwnLine: 60 36 | PointerBindsToType: false 37 | SpaceAfterControlStatementKeyword: true 38 | SpaceAfterCStyleCast: true 39 | SpaceBeforeAssignmentOperators: true 40 | SpaceInEmptyParentheses: false 41 | SpacesBeforeTrailingComments: 1 42 | SpacesInAngles: false 43 | SpacesInCStyleCastParentheses: false 44 | SpacesInParentheses: false 45 | Standard: Cpp11 46 | TabWidth: 4 47 | UseTab: Never 48 | ... 49 | 50 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | 468ae070d64c601307ee2884af9fa0f012f71d49 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | ignore: 9 | # Offical actions have moving tags like v1 10 | # that are used, so they don't need updates here 11 | - dependency-name: "actions/*" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SublimeText 2 | *.sublime-project 3 | *.sublime-workspace 4 | 5 | # VS & VSCode 6 | /\.vscode 7 | /\.vs 8 | 9 | # Jupyter 10 | /.ipynb_checkpoints 11 | 12 | # Python 13 | __pycache__ 14 | *.pyc 15 | /wheelhouse 16 | 17 | # macOS 18 | \.DS_Store 19 | 20 | # Clangd 21 | /\.cache 22 | 23 | # Default build directory 24 | /build* 25 | 26 | # Auto-generated documentation files 27 | /docs/generated/plugins_*.rst 28 | /docs/src/rendering 29 | /docs/src/inverse_rendering 30 | /docs/src/others 31 | /docs/src/quickstart 32 | /docs/src/generated 33 | /docs/resources 34 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/zlib"] 2 | path = ext/zlib 3 | url = https://github.com/mitsuba-renderer/zlib 4 | [submodule "ext/libpng"] 5 | path = ext/libpng 6 | url = https://github.com/mitsuba-renderer/libpng.git 7 | [submodule "ext/libjpeg"] 8 | path = ext/libjpeg 9 | url = https://github.com/mitsuba-renderer/libjpeg 10 | [submodule "ext/openexr"] 11 | path = ext/openexr 12 | url = https://github.com/mitsuba-renderer/openexr 13 | [submodule "ext/tinyformat"] 14 | path = ext/tinyformat 15 | url = https://github.com/mitsuba-renderer/tinyformat 16 | [submodule "ext/pugixml"] 17 | path = ext/pugixml 18 | url = https://github.com/mitsuba-renderer/pugixml 19 | [submodule "ext/asmjit"] 20 | path = ext/asmjit 21 | url = https://github.com/mitsuba-renderer/asmjit 22 | [submodule "ext/nanogui"] 23 | path = ext/nanogui 24 | url = https://github.com/mitsuba-renderer/nanogui 25 | [submodule "resources/data"] 26 | path = resources/data 27 | url = https://github.com/mitsuba-renderer/mitsuba-data 28 | shallow = true 29 | [submodule "ext/embree"] 30 | path = ext/embree 31 | url = https://github.com/mitsuba-renderer/embree 32 | [submodule "ext/ittnotify"] 33 | path = ext/ittnotify 34 | url = https://github.com/intel/IntelSEAPI 35 | [submodule "ext/fastfloat"] 36 | path = ext/fastfloat 37 | url = https://github.com/fastfloat/fast_float 38 | [submodule "tutorials"] 39 | path = tutorials 40 | url = https://github.com/mitsuba-renderer/mitsuba-tutorials 41 | shallow = true 42 | [submodule "ext/drjit"] 43 | path = ext/drjit 44 | url = https://github.com/mitsuba-renderer/drjit 45 | [submodule "ext/nanobind"] 46 | path = ext/nanobind 47 | url = https://github.com/wjakob/nanobind.git 48 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | submodules: 4 | include: all 5 | recursive: true 6 | 7 | build: 8 | os: ubuntu-22.04 9 | tools: 10 | python: "3.10" 11 | 12 | sphinx: 13 | configuration: docs/conf.py 14 | 15 | python: 16 | install: 17 | - requirements: docs/requirements.txt 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Wenzel Jakob , All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | You are under no obligation whatsoever to provide any bug fixes, patches, or 29 | upgrades to the features, functionality or performance of the source code 30 | ("Enhancements") to anyone; however, if you choose to make your Enhancements 31 | available either publicly, or directly to the author of this software, without 32 | imposing a separate written license agreement for such Enhancements, then you 33 | hereby grant the following license: a non-exclusive, royalty-free perpetual 34 | license to install, use, modify, prepare derivative works, incorporate into 35 | other computer software, distribute, and sublicense such enhancements or 36 | derivative works thereof, in binary and source code form. 37 | -------------------------------------------------------------------------------- /docs/_static/nbsphinx-gallery.css: -------------------------------------------------------------------------------- 1 | .nbsphinx-gallery { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); 4 | gap: 5px; 5 | margin-top: 1em; 6 | margin-bottom: 1em; 7 | } 8 | 9 | .nbsphinx-gallery > a { 10 | padding: 5px; 11 | border: 1px dotted currentColor; 12 | border-radius: 2px; 13 | text-align: center; 14 | } 15 | 16 | .nbsphinx-gallery > a:hover { 17 | border-style: solid; 18 | box-shadow: 0 0 11px rgba(33,33,33,.2); 19 | } 20 | 21 | .nbsphinx-gallery img { 22 | max-width: 100%; 23 | max-height: 100%; 24 | } 25 | 26 | .nbsphinx-gallery > a > div:first-child { 27 | display: flex; 28 | align-items: start; 29 | justify-content: center; 30 | height: 120px; 31 | margin-bottom: 25px; 32 | } 33 | -------------------------------------------------------------------------------- /docs/_templates/page.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "!page.html" %} 3 | 4 | {% block footer %} 5 | 6 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /docs/exts/sphinxtr/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Jeff Terrace 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY 14 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 23 | DAMAGE. 24 | -------------------------------------------------------------------------------- /docs/exts/sphinxtr/fix_equation_ref.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fixes equation references from Sphinx math domain 3 | from Equation (1) to Equation 1, which is what they 4 | should be. Must be before sphinx.ext.math* in 5 | extensions list. 6 | """ 7 | 8 | from docutils import nodes 9 | import sphinx.ext.mathbase 10 | from sphinx.ext.mathbase import displaymath, eqref 11 | 12 | def number_equations(app, doctree, docname): 13 | num = 0 14 | numbers = {} 15 | for node in doctree.traverse(displaymath): 16 | if node['label'] is not None: 17 | num += 1 18 | node['number'] = num 19 | numbers[node['label']] = num 20 | else: 21 | node['number'] = None 22 | for node in doctree.traverse(eqref): 23 | if node['target'] not in numbers: 24 | continue 25 | num = '%d' % numbers[node['target']] 26 | node[0] = nodes.Text(num, num) 27 | 28 | sphinx.ext.mathbase.number_equations = number_equations 29 | 30 | def setup(app): 31 | pass 32 | -------------------------------------------------------------------------------- /docs/exts/sphinxtr/numsec.py: -------------------------------------------------------------------------------- 1 | """ 2 | Changes section references to be the section number 3 | instead of the title of the section. 4 | """ 5 | 6 | from docutils import nodes 7 | import sphinx.domains.std 8 | 9 | class CustomStandardDomain(sphinx.domains.std.StandardDomain): 10 | 11 | def __init__(self, env): 12 | env.settings['footnote_references'] = 'superscript' 13 | sphinx.domains.std.StandardDomain.__init__(self, env) 14 | 15 | def resolve_xref(self, env, fromdocname, builder, 16 | typ, target, node, contnode): 17 | res = super(CustomStandardDomain, self).resolve_xref(env, fromdocname, builder, 18 | typ, target, node, contnode) 19 | 20 | if res is None: 21 | return res 22 | 23 | if typ == 'ref' and not node['refexplicit']: 24 | docname, labelid, sectname = self.data['labels'].get(target, ('','','')) 25 | res['refdocname'] = docname 26 | 27 | return res 28 | 29 | def doctree_resolved(app, doctree, docname): 30 | secnums = app.builder.env.toc_secnumbers 31 | for node in doctree.traverse(nodes.reference): 32 | if 'refdocname' in node: 33 | refdocname = node['refdocname'] 34 | if refdocname in secnums: 35 | secnum = secnums[refdocname] 36 | toclist = app.builder.env.tocs[refdocname] 37 | for child in node.traverse(): 38 | if isinstance(child, nodes.Text): 39 | text = child.astext() 40 | anchorname = None 41 | for refnode in toclist.traverse(nodes.reference): 42 | if refnode.astext() == text: 43 | anchorname = refnode['anchorname'] 44 | if anchorname is None: 45 | continue 46 | linktext = '.'.join(map(str, secnum[anchorname])) 47 | child.parent.replace(child, nodes.Text(linktext)) 48 | 49 | def setup(app): 50 | app.override_domain(CustomStandardDomain) 51 | app.connect('doctree-resolved', doctree_resolved) 52 | -------------------------------------------------------------------------------- /docs/exts/sphinxtr/singlehtml_toc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fixes the table of contents in singlehtml mode so that section titles have the 3 | correct section number in front. 4 | """ 5 | 6 | from docutils import nodes 7 | from sphinx import addnodes 8 | 9 | def stringize_secnum(secnum): 10 | return '.'.join(map(str, secnum)) 11 | 12 | def doctree_resolved(app, doctree, fromdocname): 13 | if app.builder.name == 'singlehtml': 14 | secnums = app.builder.env.toc_secnumbers 15 | for filenode in doctree.traverse(addnodes.start_of_file): 16 | docname = filenode['docname'] 17 | if docname not in secnums: 18 | continue 19 | 20 | doc_secnums = secnums[docname] 21 | first_title_node = filenode.next_node(nodes.title) 22 | if first_title_node is not None and '' in doc_secnums: 23 | file_secnum = stringize_secnum(doc_secnums['']) 24 | title_text_node = first_title_node.children[0] 25 | newtext = file_secnum + ' ' + title_text_node.astext() 26 | first_title_node.replace(title_text_node, nodes.Text(newtext)) 27 | 28 | for section_node in filenode.traverse(nodes.section): 29 | for id in section_node['ids']: 30 | if '#' + id in doc_secnums: 31 | subsection_num = stringize_secnum(doc_secnums['#' + id]) 32 | first_title_node = section_node.next_node(nodes.title) 33 | if first_title_node is not None: 34 | title_text_node = first_title_node.children[0] 35 | newtext = subsection_num + ' ' + title_text_node.astext() 36 | first_title_node.replace(title_text_node, nodes.Text(newtext)) 37 | 38 | def setup(app): 39 | app.connect('doctree-resolved', doctree_resolved) 40 | -------------------------------------------------------------------------------- /docs/exts/sphinxtr/singletext.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | from os import path 3 | 4 | from docutils.io import StringOutput 5 | 6 | from sphinx.util.console import bold, darkgreen, brown 7 | from sphinx.util.nodes import inline_all_toctrees 8 | from sphinx.util.osutil import ensuredir, os_path 9 | from sphinx.builders.text import TextBuilder 10 | from sphinx.writers.text import TextWriter, TextTranslator 11 | 12 | class SingleFileTextTranslator(TextTranslator): 13 | def visit_start_of_file(self, node): 14 | pass 15 | def depart_start_of_file(self, node): 16 | pass 17 | 18 | class SingleFileTextWriter(TextWriter): 19 | def translate(self): 20 | visitor = SingleFileTextTranslator(self.document, self.builder) 21 | self.document.walkabout(visitor) 22 | self.output = visitor.body 23 | 24 | class SingleFileTextBuilder(TextBuilder): 25 | """ 26 | A TextBuilder subclass that puts the whole document tree in one text file. 27 | """ 28 | name = 'singletext' 29 | copysource = False 30 | 31 | def get_outdated_docs(self): 32 | return 'all documents' 33 | 34 | def assemble_doctree(self): 35 | master = self.config.master_doc 36 | tree = self.env.get_doctree(master) 37 | tree = inline_all_toctrees( 38 | self, set(), master, tree, darkgreen, [master]) 39 | tree['docname'] = master 40 | self.env.resolve_references(tree, master, self) 41 | return tree 42 | 43 | def prepare_writing(self, docnames): 44 | self.writer = SingleFileTextWriter(self) 45 | 46 | def write(self, *ignored): 47 | docnames = self.env.all_docs 48 | 49 | self.info(bold('preparing documents... '), nonl=True) 50 | self.prepare_writing(docnames) 51 | self.info('done') 52 | 53 | self.info(bold('assembling single document... '), nonl=True) 54 | doctree = self.assemble_doctree() 55 | self.info() 56 | self.info(bold('writing... '), nonl=True) 57 | self.write_doc(self.config.master_doc, doctree) 58 | self.info('done') 59 | 60 | def setup(app): 61 | app.add_builder(SingleFileTextBuilder) 62 | -------------------------------------------------------------------------------- /docs/images/DO_NOT_PUT_FILES_HERE.txt: -------------------------------------------------------------------------------- 1 | This directory is just meant for the logo and images that are directly referenced by README.md. 2 | Any other images, figures, animations, etc. should go to '/resources/data/docs', which is a separate Git submodule that can be checked out sparsely. The goal is to avoid costly clones caused by old files appearing somewhere in the history. 3 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/logo_plain.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/docs/images/logo_plain.pdf -------------------------------------------------------------------------------- /docs/images/logo_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/docs/images/logo_plain.png -------------------------------------------------------------------------------- /docs/images/mitsuba-logo-white-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/docs/images/mitsuba-logo-white-bg.png -------------------------------------------------------------------------------- /docs/issue_template.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Summary 10 | 11 | 12 | 13 | ## System configuration 14 | 15 | 16 | 17 | 18 | 19 | System information: 20 | 21 | OS: ... 22 | CPU: ... 23 | GPU: ... 24 | Python version: ... 25 | LLVM version: ... 26 | CUDA version: ... 27 | NVidia driver: ... 28 | 29 | Dr.Jit version: ... 30 | Mitsuba version: ... 31 | Compiled with: ... 32 | Variants compiled: ... 33 | 34 | ## Description 35 | 36 | 37 | 38 | ## Steps to reproduce 39 | 40 | 41 | 42 | 1. ... 43 | 2. ... 44 | -------------------------------------------------------------------------------- /docs/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | Fixes # (issue) 8 | 9 | ## Testing 10 | 11 | 12 | 13 | ## Checklist 14 | 15 | 16 | 17 | - [ ] My code follows the [style guidelines](https://mitsuba.readthedocs.io/en/latest/src/developer_guide.html#coding-style) of this project 18 | - [ ] My changes generate no new warnings 19 | - [ ] My code also compiles for `cuda_*` and `llvm_*` variants. If you can't test this, please leave below 20 | - [ ] I have commented my code 21 | - [ ] I have made corresponding changes to the documentation 22 | - [ ] I have added tests that prove my fix is effective or that my feature works 23 | - [ ] I cleaned the commit history and removed any "Merge" commits 24 | - [ ] I give permission that the Mitsuba 3 project may redistribute my contributions under the terms of its [license](https://github.com/mitsuba-renderer/mitsuba/blob/master/LICENSE) -------------------------------------------------------------------------------- /docs/release.rst: -------------------------------------------------------------------------------- 1 | How to make a new release? 2 | -------------------------- 3 | 4 | 1. Ensure that the most recent version of Mitsuba is checked out (including all 5 | submodules). 6 | 7 | 2. Regenerate the documentation using the `mkoc`, `mkdoc-api` and `docstrings` 8 | targets and commit the result. Do this with the following command in your 9 | build folder: ``ninja docstrings && ninja && ninja mkdoc-api mkdoc``. 10 | 11 | 3. Check that the ``nanobind`` dependency version in ``pyroject.toml`` (build 12 | requirement) matches the version used in the submodule. 13 | 14 | 4. Update the ``drjit`` dependency version (build requirement and dependency) 15 | in ``pyroject.toml``, it must match ``ext/drjit/include/drjit/fwd.h``. That 16 | version of ``drjit`` must also be available on PyPI. 17 | 18 | 5. Ensure that the changelog is up to date in ``docs/release_notes.rst``. 19 | 20 | 6. Verify that the CI is currently green on all platforms. 21 | 22 | 7. Run the GHA "Build Python Wheels" with option "0". This effectively is a dry 23 | run of the wheel creation process. 24 | 25 | 8. If the action failed, fix whatever broke in the build process. If it succeded 26 | continue. 27 | 28 | 9. Update the version number in ``include/mitsuba/mitsuba.h``. 29 | 30 | 10. Add release number and date to ``docs/release_notes.rst``. 31 | 32 | 11. Regenerate the documentation again using the same command: 33 | ``ninja docstrings && ninja && ninja mkdoc-api mkdoc``. 34 | 35 | 12. Commit: ``git commit -am "vX.Y.Z release"`` 36 | 37 | 13. Tag: ``git tag -a vX.Y.Z -m "vX.Y.Z release"`` 38 | 39 | 14. Push: ``git push`` and ``git push --tags`` 40 | 41 | 15. Run the GHA "Build Python Wheels" with option "1". 42 | 43 | 16. Check that the new version is available on 44 | `readthedocs `__. 45 | 46 | 17. Create a `release on GitHub `__ 47 | from the tag created at step 10. The changelog can be copied from step 2. 48 | 49 | 18. Checkout the ``stable`` branch and run ``git pull --ff-only origin vX.Y.Z`` 50 | and ``git push`` 51 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==8.1.3 2 | IPython==8.8.0 3 | pygments-mathematica==0.3.5 4 | pybtex==0.24.0 5 | nbsphinx==0.9.5 6 | furo==2024.8.6 7 | sphinxcontrib-bibtex==2.6.3 8 | sphinxcontrib-youtube==1.4.1 9 | sphinx-design==0.6.1 10 | sphinx-gallery==0.18.0 11 | sphinx-copybutton==0.5.2 12 | sphinx-hoverxref==1.4.1 13 | sphinx_tabs==3.4.5 14 | sphinx-toolbox==3.8.1 15 | -------------------------------------------------------------------------------- /docs/resources: -------------------------------------------------------------------------------- 1 | ../resources -------------------------------------------------------------------------------- /docs/src/api_reference.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | .. _sec-api: 3 | 4 | .. image:: ../resources/data/docs/images/banners/banner_07.jpg 5 | :width: 100% 6 | :align: center 7 | 8 | 9 | API reference 10 | ============= 11 | 12 | Overview 13 | -------- 14 | 15 | This API reference documentation was automatically generated using the `Autodoc 16 | `_ Sphinx 17 | extension. 18 | 19 | Autodoc automatically processes the documentation of Mitsuba's Python bindings, 20 | hence all C++ function and class signatures are documented through their Python 21 | counterparts. Mitsuba's bindings mimic the C++ API as closely as possible, 22 | hence this documentation should still prove valuable even for C++ developers. 23 | 24 | .. include:: generated/mitsuba_api.rst 25 | -------------------------------------------------------------------------------- /docs/src/developer_guide/documentation.rst: -------------------------------------------------------------------------------- 1 | .. _sec-writing-documentation: 2 | 3 | Writing documentation 4 | ===================== 5 | 6 | Additional packages are required to generate the HTML documentation which can 7 | be installed running the following command: 8 | 9 | .. code-block:: bash 10 | 11 | pip install -r docs/requirements.txt 12 | 13 | If not already done, run `cmake` in the build folder to generate the build 14 | targets related to the documentation: 15 | 16 | .. code-block:: bash 17 | 18 | cd build 19 | cmake -GNinja .. 20 | 21 | To generate the HTML documentation, build the `mkdoc` target as follow: 22 | 23 | .. code-block:: bash 24 | 25 | ninja mkdoc 26 | 27 | This process will generate the documentation HTML files into the `build/html` 28 | folder. 29 | 30 | API references generation 31 | ------------------------- 32 | 33 | The API references need to be generated manually executing the following command: 34 | 35 | .. code-block:: bash 36 | 37 | ninja mkdoc-api 38 | ninja mkdoc # Rebuild the documentation to update the API references 39 | 40 | Notebook tutorials 41 | ------------------ 42 | 43 | We are using the `nbsphinx `_ Sphinx extension 44 | to render our tutorials in the online documentation. 45 | 46 | The thumbnail of a notebook in the gallery can be the output image of a cell in 47 | the notebook. For this, simply add the following to the metadata of that cell: 48 | 49 | .. code-block:: json 50 | 51 | { 52 | "nbsphinx-thumbnail": {} 53 | } 54 | 55 | 56 | In order to hide a cell of a notebook in the documentation, add the following to 57 | the metadata of that cell: 58 | 59 | .. code-block:: json 60 | 61 | { 62 | "nbsphinx": "hidden" 63 | } 64 | -------------------------------------------------------------------------------- /docs/src/generated: -------------------------------------------------------------------------------- 1 | ../generated -------------------------------------------------------------------------------- /docs/src/how_to_guides: -------------------------------------------------------------------------------- 1 | ../../tutorials/how_to_guides -------------------------------------------------------------------------------- /docs/src/how_to_guides.rst: -------------------------------------------------------------------------------- 1 | .. image:: ../resources/data/docs/images/banners/banner_04.jpg 2 | :width: 100% 3 | :align: center 4 | 5 | How-to Guides 6 | ============= 7 | 8 | Overview 9 | -------- 10 | 11 | The following guides explain important features of the library in details and 12 | are dedicated to users with some experience. Unlike the :ref:`sec-api `, they are by step-by-step guide that explore what it is possible to do 14 | with specific abstractions of Mitsuba. 15 | 16 | Guides 17 | ------ 18 | 19 | .. toctree:: 20 | :maxdepth: 1 21 | :glob: 22 | 23 | how_to_guides/transformation_toolbox 24 | how_to_guides/image_io_and_manipulation 25 | how_to_guides/mesh_io_and_manipulation 26 | how_to_guides/use_optimizers 27 | -------------------------------------------------------------------------------- /docs/src/inverse_rendering_tutorials.rst: -------------------------------------------------------------------------------- 1 | .. image:: ../../resources/data/docs/images/banners/banner_04.jpg 2 | :width: 100% 3 | :align: center 4 | 5 | Inverse rendering tutorials 6 | =========================== 7 | 8 | Mitsuba 3 can be used to solve inverse problems involving light using a 9 | technique known as differentiable rendering. This enables us to estimate 10 | physical attributes of a scene, e.g., reflectance, geometry, and lighting, from 11 | images. The following tutorials will teach you how to use Mitsuba 3 in such 12 | applications: 13 | 14 | .. nbgallery:: 15 | 16 | inverse_rendering/gradient_based_opt 17 | inverse_rendering/forward_inverse_rendering 18 | inverse_rendering/caustics_optimization 19 | inverse_rendering/object_pose_estimation 20 | inverse_rendering/projective_sampling_integrators 21 | inverse_rendering/volume_optimization 22 | inverse_rendering/shape_optimization 23 | inverse_rendering/radiance_field_reconstruction 24 | inverse_rendering/polarizer_optimization 25 | inverse_rendering/pytorch_mitsuba_interoperability 26 | -------------------------------------------------------------------------------- /docs/src/key_topics.rst: -------------------------------------------------------------------------------- 1 | .. image:: ../resources/data/docs/images/banners/banner_02.jpg 2 | :width: 100% 3 | :align: center 4 | 5 | Key Topics 6 | ========== 7 | 8 | Overview 9 | -------- 10 | 11 | The following document aim at clarifying a particular part of the system or the 12 | background theory. They will often be referred by the tutorials and guides for 13 | in depth knowledge about a specific subject. 14 | 15 | Topics 16 | ------ 17 | 18 | .. toctree:: 19 | :maxdepth: 1 20 | :glob: 21 | 22 | key_topics/variants 23 | key_topics/scene_format 24 | key_topics/differences 25 | key_topics/polarization -------------------------------------------------------------------------------- /docs/src/others_tutorials.rst: -------------------------------------------------------------------------------- 1 | .. image:: ../../resources/data/docs/images/banners/banner_02.jpg 2 | :width: 100% 3 | :align: center 4 | 5 | Other tutorials 6 | =============== 7 | 8 | Mitsuba 3 is more than a retargetable & differentiable renderer. When used from 9 | Python, it can be viewed as a general rendering toolkit. In this section you 10 | will find tutorials that will teach you how to use Mitsuba 3 in contexts other 11 | than rendering and inverse rendering. 12 | 13 | .. nbgallery:: 14 | 15 | others/bsdf_deep_dive 16 | others/granular_phase_function 17 | others/custom_plugin 18 | -------------------------------------------------------------------------------- /docs/src/plugin_reference/section_films.rst: -------------------------------------------------------------------------------- 1 | .. _sec-films: 2 | 3 | Films 4 | ===== 5 | 6 | A film defines how conducted measurements are stored and converted into the final 7 | output file that is written to disk at the end of the rendering process. 8 | 9 | In the XML scene description language, a normal film configuration might look 10 | as follows: 11 | 12 | .. tabs:: 13 | .. code-tab:: xml 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | .. code-tab:: python 34 | 35 | 'type': 'scene', 36 | 37 | # .. scene contents .. 38 | 39 | 'sensor_id': { 40 | 'type': '' 41 | 42 | # Write to a high dynamic range EXR image 43 | 'film_id': { 44 | 'type': 'hdrfilm', 45 | # Specify the desired resolution (e.g. full HD) 46 | 'width': 1920, 47 | 'height': 1080, 48 | # Use a Gaussian reconstruction filter 49 | 'filter': { 'type': 'gaussian' } 50 | } 51 | } 52 | 53 | The ```` plugin should be instantiated nested inside a ```` 54 | declaration. Note how the output filename is never specified---it is automatically 55 | inferred from the scene filename and can be manually overridden by passing the 56 | configuration parameter ``-o`` to the ``mitsuba`` executable when rendering 57 | from the command line. 58 | -------------------------------------------------------------------------------- /docs/src/plugin_reference/section_phase.rst: -------------------------------------------------------------------------------- 1 | .. _sec-phasefunctions: 2 | 3 | Phase functions 4 | =============== 5 | 6 | This section contains a description of all implemented medium scattering models, 7 | which are also known as phase functions. These are very similar in principle to 8 | surface scattering models (or BSDFs), and essentially describe where light 9 | travels after hitting a particle within the medium. Currently, only the most 10 | commonly used models for smoke, fog, and other homogeneous media are implemented. 11 | -------------------------------------------------------------------------------- /docs/src/plugin_reference/section_rfilters.rst: -------------------------------------------------------------------------------- 1 | .. _sec-rfilters: 2 | 3 | Reconstruction filters 4 | ====================== 5 | 6 | Image reconstruction filters are responsible for converting a series of radiance samples generated 7 | jointly by the sampler and integrator into the final output image that will be written to disk at 8 | the end of a rendering process. This section gives a brief overview of the reconstruction filters 9 | that are available in Mitsuba. There is no universally superior filter, and the final choice depends 10 | on a trade-off between sharpness, ringing, and aliasing, and computational efficiency. 11 | 12 | Desirable properties of a reconstruction filter are that it sharply captures all of the details 13 | that are displayable at the requested image resolution, while avoiding aliasing and ringing. 14 | Aliasing is the incorrect leakage of high-frequency into low-frequency detail, and ringing denotes 15 | oscillation artifacts near discontinuities, such as a light-shadow transition. -------------------------------------------------------------------------------- /docs/src/plugin_reference/section_samplers.rst: -------------------------------------------------------------------------------- 1 | .. _sec-samplers: 2 | 3 | Samplers 4 | ======== 5 | 6 | When rendering an image, Mitsuba 3 has to solve a high-dimensional integration problem that involves 7 | the geometry, materials, lights, and sensors that make up the scene. Because of the mathematical 8 | complexity of these integrals, it is generally impossible to solve them analytically — instead, they 9 | are solved numerically by evaluating the function to be integrated at a large number of different 10 | positions referred to as samples. Sample generators are an essential ingredient to this process: 11 | they produce points in a (hypothetical) infinite dimensional hypercube :math:`[0, 1]^{\infty}` 12 | that constitute the canonical representation of these samples. 13 | 14 | To do its work, a rendering algorithm, or integrator, will send many queries to the sample 15 | generator. Generally, it will request subsequent 1D or 2D components of this infinite-dimensional 16 | *point* and map them into a more convenient space (for instance, positions on surfaces). This allows 17 | it to construct light paths to eventually evaluate the flow of light through the scene. 18 | -------------------------------------------------------------------------------- /docs/src/plugin_reference/section_shapes.rst: -------------------------------------------------------------------------------- 1 | .. _sec-shapes: 2 | 3 | Shapes 4 | ====== 5 | 6 | This section presents an overview of the shape plugins that are released along with the renderer. 7 | 8 | In Mitsuba 3, shapes define surfaces that mark transitions between different types of materials. For 9 | instance, a shape could describe a boundary between air and a solid object, such as a piece of rock. 10 | Alternatively, a shape can mark the beginning of a region of space that isn’t solid at all, but 11 | rather contains a participating medium, such as smoke or steam. Finally, a shape can be used to 12 | create an object that emits light on its own. 13 | 14 | Shapes are usually declared along with a surface scattering model named *BSDF* (see the :ref:`respective section `). This BSDF characterizes what happens at the surface. In the XML scene description language, this might look like the following: 15 | 16 | .. tabs:: 17 | .. code-tab:: xml 18 | 19 | 20 | 21 | 22 | 23 | .. shape parameters .. 24 | 25 | 26 | .. bsdf parameters .. 27 | 28 | 29 | 34 | 35 | 36 | 37 | .. code-tab:: python 38 | 39 | 'type': 'scene', 40 | 41 | # .. scene contents .. 42 | 43 | 'shape_id': { 44 | 'type': '', 45 | 'bsdf_id': { 46 | 'type': '', 47 | # .. bsdf parameters .. 48 | } 49 | 50 | # Alternatively, reference a named BSDF that had been declared previously 51 | 'bsdf_id' : { 52 | 'type' : 'ref', 53 | 'id' : 'some_bsdf_id' 54 | } 55 | } 56 | 57 | The following subsections discuss the available shape types in greater detail. -------------------------------------------------------------------------------- /docs/src/plugin_reference/section_volumes.rst: -------------------------------------------------------------------------------- 1 | .. _sec-volume: 2 | 3 | Volumes 4 | ======= 5 | 6 | This section covers the different types of volume data sources included with 7 | Mitsuba. These plug-ins are intended to be used together with the medium 8 | plugins and provide three-dimensional spatially varying density and albedo fields. 9 | 10 | -------------------------------------------------------------------------------- /docs/src/quickstart: -------------------------------------------------------------------------------- 1 | ../../tutorials/quickstart -------------------------------------------------------------------------------- /docs/src/rendering_tutorials.rst: -------------------------------------------------------------------------------- 1 | .. image:: ../../resources/data/docs/images/banners/banner_03.jpg 2 | :width: 100% 3 | :align: center 4 | 5 | Rendering tutorials 6 | =================== 7 | 8 | The best place to start is with user-friendly tutorials that will show you how 9 | to use Mitsuba 3 with complete, end-to-end examples. The following tutorials 10 | cover the basic operations ones might need to know when it comes to rendering 11 | with Mitsuba 3: 12 | 13 | .. nbgallery:: 14 | 15 | quickstart/mitsuba_quickstart 16 | rendering/editing_a_scene 17 | rendering/multi_view_rendering 18 | rendering/scripting_renderer 19 | rendering/polarized_rendering 20 | -------------------------------------------------------------------------------- /docs/zz_bibliography.rst: -------------------------------------------------------------------------------- 1 | .. only:: html or text 2 | 3 | Bibliography 4 | ============ 5 | 6 | .. bibliography:: references.bib -------------------------------------------------------------------------------- /ext/rgb2spec/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13...3.18) 2 | project(rgb2spec) 3 | 4 | # Set a default build configuration (Release) 5 | if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 6 | message(STATUS "Setting build type to 'Release' as none was specified.") 7 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) 8 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" 9 | "MinSizeRel" "RelWithDebInfo") 10 | endif() 11 | 12 | add_library(rgb2spec STATIC rgb2spec.c) 13 | set_property(TARGET rgb2spec PROPERTY POSITION_INDEPENDENT_CODE ON) 14 | 15 | add_executable(rgb2spec_opt rgb2spec_opt.cpp) 16 | set_target_properties(rgb2spec_opt PROPERTIES 17 | CXX_STANDARD 11 18 | CXX_STANDARD_REQUIRED YES 19 | CXX_EXTENSIONS NO) 20 | 21 | if (NOT APPLE) 22 | find_package(OpenMP) 23 | if(OpenMP_CXX_FOUND) 24 | target_link_libraries(rgb2spec_opt PRIVATE OpenMP::OpenMP_CXX) 25 | endif() 26 | endif() 27 | 28 | add_custom_command( 29 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/srgb.coeff 30 | DEPENDS rgb2spec_opt 31 | COMMAND $ 64 ${CMAKE_CURRENT_BINARY_DIR}/srgb.coeff 32 | ) 33 | 34 | add_custom_target( 35 | rgb2spec_opt_run 36 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/srgb.coeff 37 | ) 38 | -------------------------------------------------------------------------------- /ext/rgb2spec/rgb2spec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(__SSE4_2__) || defined(__AVX__) 6 | # include 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | extern "C" 11 | { 12 | #endif 13 | 14 | /// How many polynomial coefficients? 15 | #define RGB2SPEC_N_COEFFS 3 16 | 17 | /// Underlying representation 18 | typedef struct { 19 | uint32_t res; 20 | float *scale; 21 | float *data; 22 | } RGB2Spec; 23 | 24 | /// Load a RGB2Spec model from disk 25 | RGB2Spec *rgb2spec_load(const char *filename); 26 | 27 | /// Release all memory associated with a RGB2Spec model 28 | void rgb2spec_free(RGB2Spec *model); 29 | 30 | /// Convert an RGB value into a RGB2Spec coefficient representation 31 | void rgb2spec_fetch(RGB2Spec *model, float rgb[3], float out[RGB2SPEC_N_COEFFS]); 32 | 33 | /// Evaluate the model for a given wavelength 34 | float rgb2spec_eval_precise(float coeff[RGB2SPEC_N_COEFFS], float lambda); 35 | 36 | /// Evaluate the model for a given wavelength (fast, with recip. square root) 37 | float rgb2spec_eval_fast(float coeff[RGB2SPEC_N_COEFFS], float lambda); 38 | 39 | #if defined(__SSE4_2__) 40 | /// SSE 4.2 version -- evaluates 4 wavelengths at once 41 | __m128 rgb2spec_eval_sse(float coeff[RGB2SPEC_N_COEFFS], __m128 lambda); 42 | #endif 43 | 44 | #if defined(__AVX__) 45 | /// AVX version -- evaluates 8 wavelengths at once 46 | __m256 rgb2spec_eval_avx(float coeff[RGB2SPEC_N_COEFFS], __m256 lambda); 47 | #endif 48 | 49 | #if defined(__AVX512F__) 50 | /// AVX512 version -- evaluates 16 wavelengths at once 51 | __m512 rgb2spec_eval_avx512(float coeff[RGB2SPEC_N_COEFFS], __m512 lambda); 52 | #endif 53 | 54 | #ifdef __cplusplus 55 | } // extern "C" 56 | #endif 57 | -------------------------------------------------------------------------------- /include/mitsuba/core/dstream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | NAMESPACE_BEGIN(mitsuba) 7 | 8 | /** \brief \ref Stream implementation that never writes to disk, but keeps track 9 | * of the size of the content being written. 10 | * It can be used, for example, to measure the precise amount of memory needed 11 | * to store serialized content. 12 | */ 13 | class MI_EXPORT_LIB DummyStream : public Stream { 14 | public: 15 | DummyStream(); 16 | 17 | ~DummyStream() = default; 18 | 19 | /** \brief Closes the stream. 20 | * No further read or write operations are permitted. 21 | * 22 | * This function is idempotent. 23 | * It may be called automatically by the destructor. 24 | */ 25 | virtual void close() override; 26 | 27 | /// Whether the stream is closed (no read or write are then permitted). 28 | virtual bool is_closed() const override; 29 | 30 | // ========================================================================= 31 | //! @{ \name Implementation of the Stream interface 32 | // ========================================================================= 33 | 34 | virtual void read(void *, size_t) override; 35 | virtual void write(const void *, size_t size) override; 36 | virtual void seek(size_t pos) override; 37 | virtual void truncate(size_t size) override; 38 | virtual size_t tell() const override; 39 | virtual size_t size() const override; 40 | virtual void flush() override; 41 | virtual bool can_write() const override; 42 | virtual bool can_read() const override; 43 | 44 | //! @} 45 | // ========================================================================= 46 | 47 | MI_DECLARE_CLASS() 48 | private: 49 | /// Size of all data written to the stream 50 | size_t m_size; 51 | /** \brief Current position in the "virtual" stream (even though nothing 52 | * is ever written, we need to maintain consistent positioning). 53 | */ 54 | size_t m_pos; 55 | /// Whether the stream has been closed. 56 | bool m_is_closed; 57 | 58 | }; 59 | 60 | NAMESPACE_END(mitsuba) 61 | -------------------------------------------------------------------------------- /include/mitsuba/core/jit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(DRJIT_X86_64) 6 | # if defined(__GNUG__) && !defined(__clang__) 7 | # pragma GCC diagnostic push 8 | # pragma GCC diagnostic ignored "-Wbool-operation" 9 | # pragma GCC diagnostic ignored "-Wbool-compare" 10 | # elif defined(_MSC_VER) 11 | # pragma warning(push) 12 | # pragma warning(disable : 4804) // warning C4804: '~': unsafe use of type 'bool' in operation 13 | # pragma warning(disable : 4245) // warning C4245: 'argument': conversion from 'int' to 'uint32_t', signed/unsigned mismatch 14 | # endif 15 | 16 | # include 17 | 18 | # if defined(__GNUG__) && !defined(__clang__) 19 | # pragma GCC diagnostic pop 20 | # elif defined(_MSC_VER) 21 | # pragma warning(pop) 22 | # endif 23 | #endif 24 | 25 | #include 26 | 27 | NAMESPACE_BEGIN(mitsuba) 28 | 29 | struct MI_EXPORT_LIB Jit { 30 | std::mutex mutex; 31 | #if defined(DRJIT_X86_64) 32 | asmjit::JitRuntime runtime; 33 | #endif 34 | 35 | /** 36 | * \brief Statically initialize the JIT runtime 37 | * 38 | * This function also does a runtime-check to ensure that the host 39 | * processor supports all instruction sets which were selected at compile 40 | * time. If not, the application is terminated via ``abort()``. 41 | */ 42 | static void static_initialization(); 43 | 44 | /// Release all memory used by JIT-compiled routines 45 | static void static_shutdown(); 46 | 47 | static Jit *get_instance(); 48 | 49 | private: 50 | Jit(); 51 | Jit(const Jit &) = delete; 52 | }; 53 | 54 | NAMESPACE_END(mitsuba) 55 | -------------------------------------------------------------------------------- /include/mitsuba/core/mmap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | NAMESPACE_BEGIN(mitsuba) 6 | 7 | /** 8 | * \brief Basic cross-platform abstraction for memory mapped files 9 | * 10 | * \remark The Python API has one additional constructor 11 | * MemoryMappedFile(filename, array), which creates a new 12 | * file, maps it into memory, and copies the array contents. 13 | */ 14 | class MI_EXPORT_LIB MemoryMappedFile : public Object { 15 | public: 16 | /// Create a new memory-mapped file of the specified size 17 | MemoryMappedFile(const fs::path &filename, size_t size); 18 | 19 | /// Map the specified file into memory 20 | MemoryMappedFile(const fs::path &filename, bool write = false); 21 | 22 | /// Release all resources 23 | virtual ~MemoryMappedFile(); 24 | 25 | /// Return a pointer to the file contents in memory 26 | void *data(); 27 | 28 | /// Return a pointer to the file contents in memory (const version) 29 | const void *data() const; 30 | 31 | /// Return the size of the mapped region 32 | size_t size() const; 33 | 34 | /** 35 | * \brief Resize the memory-mapped file 36 | * 37 | * This involves remapping the file, which will 38 | * generally change the pointer obtained via data() 39 | */ 40 | void resize(size_t size); 41 | 42 | /// Return the associated filename 43 | const fs::path &filename() const; 44 | 45 | /// Return whether the mapped memory region can be modified 46 | bool can_write() const; 47 | 48 | /// Return a string representation 49 | std::string to_string() const override; 50 | 51 | /** 52 | * \brief Create a temporary memory-mapped file 53 | * 54 | * \remark When closing the mapping, the file is automatically deleted. 55 | * Mitsuba additionally informs the OS that any outstanding changes 56 | * that haven't yet been written to disk can be discarded 57 | * (Linux/OSX only). 58 | */ 59 | static ref create_temporary(size_t size); 60 | 61 | MI_DECLARE_CLASS() 62 | protected: 63 | /// Internal constructor 64 | MemoryMappedFile(); 65 | 66 | private: 67 | struct MemoryMappedFilePrivate; 68 | std::unique_ptr d; 69 | }; 70 | 71 | NAMESPACE_END(mitsuba) 72 | -------------------------------------------------------------------------------- /include/mitsuba/core/progress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | NAMESPACE_BEGIN(mitsuba) 7 | 8 | /** 9 | * \brief General-purpose progress reporter 10 | * 11 | * This class is used to track the progress of various operations that might 12 | * take longer than a second or so. It provides interactive feedback when 13 | * Mitsuba is run on the console, via the OpenGL GUI, or in Jupyter Notebook. 14 | */ 15 | class MI_EXPORT_LIB ProgressReporter : public Object { 16 | public: 17 | /** 18 | * \brief Construct a new progress reporter. 19 | * \param label 20 | * An identifying name for the operation taking place (e.g. "Rendering") 21 | * \param ptr 22 | * Custom pointer payload to be delivered as part of progress messages 23 | */ 24 | ProgressReporter(const std::string &label, void *payload = nullptr); 25 | 26 | /// Destructor 27 | ~ProgressReporter(); 28 | 29 | /// Update the progress to \c progress (which should be in the range [0, 1]) 30 | void update(float progress); 31 | 32 | MI_DECLARE_CLASS() 33 | 34 | protected: 35 | Timer m_timer; 36 | std::string m_label; 37 | std::string m_line; 38 | size_t m_bar_start; 39 | size_t m_bar_size; 40 | size_t m_last_update; 41 | float m_last_progress; 42 | void *m_payload; 43 | }; 44 | 45 | NAMESPACE_END(mitsuba) 46 | -------------------------------------------------------------------------------- /include/mitsuba/core/simd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | NAMESPACE_BEGIN(mitsuba) 9 | 10 | /// Convenience function which computes an array size/type suffix (like '2u' or '3fP') 11 | template std::string type_suffix() { 12 | using S = dr::scalar_t; 13 | using V = dr::value_t; 14 | 15 | std::string id = std::to_string(dr::size_v); 16 | 17 | if constexpr (dr::is_floating_point_v) { 18 | if constexpr (std::is_same_v) 19 | id += 'h'; 20 | else if constexpr (std::is_same_v) 21 | id += 'f'; 22 | else 23 | id += 'd'; 24 | } else { 25 | if constexpr (dr::is_signed_v) 26 | id += 'i'; 27 | else 28 | id += 'u'; 29 | } 30 | 31 | if constexpr (dr::is_diff_v) 32 | id += 'D'; 33 | 34 | if constexpr (dr::is_packed_array_v) 35 | id += 'P'; 36 | else if constexpr (dr::is_llvm_v) 37 | id += 'L'; 38 | else if constexpr (dr::is_cuda_v) 39 | id += 'C'; 40 | else if constexpr (dr::is_dynamic_array_v) 41 | id += 'X'; 42 | 43 | return id; 44 | } 45 | 46 | NAMESPACE_END(mitsuba) 47 | -------------------------------------------------------------------------------- /include/mitsuba/core/tensor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | NAMESPACE_BEGIN(mitsuba) 8 | 9 | /** 10 | * \brief Simple exchange format for tensor data of arbitrary rank and size 11 | * 12 | * This class provides convenient memory-mapped read-only access to tensor 13 | * data, usually exported from NumPy. 14 | */ 15 | class MI_EXPORT_LIB TensorFile : public MemoryMappedFile { 16 | public: 17 | 18 | /// Information about the specified field 19 | struct Field { 20 | /// Data type (uint32, float, ...) of the tensor 21 | Struct::Type dtype; 22 | 23 | /// Offset within the file 24 | size_t offset; 25 | 26 | /// Specifies both rank and size along each dimension 27 | std::vector shape; 28 | 29 | /// Const pointer to the start of the tensor 30 | const void *data; 31 | }; 32 | 33 | /// Map the specified file into memory 34 | TensorFile(const fs::path &filename); 35 | 36 | /// Destructor 37 | ~TensorFile(); 38 | 39 | /// Does the file contain a field of the specified name? 40 | bool has_field(const std::string &name) const; 41 | 42 | /// Return a data structure with information about the specified field 43 | const Field &field(const std::string &name) const; 44 | 45 | /// Return a human-readable summary 46 | std::string to_string() const override; 47 | 48 | MI_DECLARE_CLASS() 49 | 50 | private: 51 | std::unordered_map m_fields; 52 | }; 53 | 54 | NAMESPACE_END(mitsuba) 55 | -------------------------------------------------------------------------------- /include/mitsuba/core/timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | NAMESPACE_BEGIN(mitsuba) 7 | 8 | class Timer { 9 | public: 10 | using Unit = std::chrono::milliseconds; 11 | 12 | Timer() { 13 | start = std::chrono::system_clock::now(); 14 | } 15 | 16 | size_t value() const { 17 | auto now = std::chrono::system_clock::now(); 18 | auto duration = std::chrono::duration_cast(now - start); 19 | return (size_t) duration.count(); 20 | } 21 | 22 | size_t reset() { 23 | auto now = std::chrono::system_clock::now(); 24 | auto duration = std::chrono::duration_cast(now - start); 25 | start = now; 26 | return (size_t) duration.count(); 27 | } 28 | 29 | void begin_stage(const std::string &name) { 30 | reset(); 31 | std::cout << name << " .. "; 32 | std::cout.flush(); 33 | } 34 | 35 | void end_stage(const std::string &str = "") { 36 | std::cout << "done. (took " << util::time_string((float) value()); 37 | if (!str.empty()) 38 | std::cout << ", " << str; 39 | std::cout << ")" << std::endl; 40 | } 41 | private: 42 | std::chrono::system_clock::time_point start; 43 | }; 44 | 45 | NAMESPACE_END(mitsuba) 46 | -------------------------------------------------------------------------------- /include/mitsuba/mitsuba.h: -------------------------------------------------------------------------------- 1 | /* 2 | Mitsuba 3: A Retargetable Forward and Inverse Renderer 3 | Copyright 2021, Realistic Graphics Lab, EPFL. 4 | 5 | All rights reserved. Use of this source code is governed by a 6 | BSD-style license that can be found in the LICENSE.txt file. 7 | */ 8 | 9 | #pragma once 10 | 11 | #define MI_VERSION_MAJOR 3 12 | #define MI_VERSION_MINOR 6 13 | #define MI_VERSION_PATCH 4 14 | 15 | #define MI_STRINGIFY(x) #x 16 | #define MI_TOSTRING(x) MI_STRINGIFY(x) 17 | 18 | /// Current release of Mitsuba 19 | #define MI_VERSION \ 20 | MI_TOSTRING(MI_VERSION_MAJOR) "." \ 21 | MI_TOSTRING(MI_VERSION_MINOR) "." \ 22 | MI_TOSTRING(MI_VERSION_PATCH) 23 | 24 | /// Year of the current release 25 | #define MI_YEAR "2024" 26 | 27 | /// Authors list 28 | #define MI_AUTHORS "Realistic Graphics Lab, EPFL" 29 | 30 | #include 31 | #include 32 | -------------------------------------------------------------------------------- /include/mitsuba/render/optix/bbox.cuh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef __CUDACC__ 4 | # include 5 | #endif 6 | 7 | namespace optix { 8 | struct BoundingBox3f { 9 | BoundingBox3f() = default; 10 | #ifndef __CUDACC__ 11 | template 12 | BoundingBox3f(const mitsuba::BoundingBox> &b) { 13 | min[0] = (float) b.min[0]; 14 | min[1] = (float) b.min[1]; 15 | min[2] = (float) b.min[2]; 16 | max[0] = (float) b.max[0]; 17 | max[1] = (float) b.max[1]; 18 | max[2] = (float) b.max[2]; 19 | } 20 | #endif 21 | float min[3]; 22 | float max[3]; 23 | }; 24 | } // end namespace optix 25 | -------------------------------------------------------------------------------- /include/mitsuba/render/optix/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __CUDACC__ 4 | # include 5 | #else 6 | # include 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | /// Stores information about a Shape on the Optix side 13 | struct OptixHitGroupData { 14 | /// Shape id in Dr.Jit's pointer registry 15 | uint32_t shape_registry_id; 16 | /// Pointer to the memory region of Shape data (e.g. \c OptixSphereData ) 17 | void* data; 18 | }; 19 | 20 | template 21 | struct alignas(OPTIX_SBT_RECORD_ALIGNMENT) SbtRecord { 22 | char header[OPTIX_SBT_RECORD_HEADER_SIZE]; 23 | T data; 24 | SbtRecord(const T &data) : data(data) { } 25 | }; 26 | 27 | struct alignas(OPTIX_SBT_RECORD_ALIGNMENT) EmptySbtRecord { 28 | char header[OPTIX_SBT_RECORD_HEADER_SIZE]; 29 | }; 30 | 31 | using RayGenSbtRecord = EmptySbtRecord; 32 | using MissSbtRecord = EmptySbtRecord; 33 | using HitGroupSbtRecord = SbtRecord; 34 | 35 | #ifdef __CUDACC__ 36 | 37 | /// Useful constants 38 | constexpr float Pi = float(3.14159265358979323846); 39 | constexpr float TwoPi = float(6.28318530717958647692); 40 | constexpr float InvPi = float(0.31830988618379067154); 41 | constexpr float InvTwoPi = float(0.15915494309189533577); 42 | constexpr float SqrtTwo = float(1.41421356237309504880); 43 | 44 | using namespace optix; 45 | 46 | /// Write PreliminaryIntersection fields to the data pointers stored in the OptixParams 47 | __device__ void 48 | set_preliminary_intersection_to_payload(float t, 49 | const Vector2f &prim_uv, 50 | unsigned int prim_index, 51 | unsigned int shape_registry_id) { 52 | optixSetPayload_0(__float_as_int(t)); 53 | optixSetPayload_1(__float_as_int(prim_uv[0])); 54 | optixSetPayload_2(__float_as_int(prim_uv[1])); 55 | optixSetPayload_3(prim_index); 56 | optixSetPayload_4(shape_registry_id); 57 | 58 | // Instance index is initialized to 0 when there is no instancing in the scene 59 | if (optixGetPayload_5() > 0) 60 | optixSetPayload_5(optixGetInstanceId()); 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /include/mitsuba/render/optix/math.cuh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __CUDACC__ 4 | #define DEVICE __forceinline__ __device__ 5 | 6 | template 7 | DEVICE T sqr(const T& x) { 8 | return x * x; 9 | } 10 | 11 | DEVICE bool solve_quadratic(float a, float b, float c, float& x0, float&x1) { 12 | bool linear_case = (a == 0.f); 13 | // For linear eq, we require b != 0 14 | if (linear_case && b == 0.f) 15 | return false; 16 | 17 | x0 = x1 = -c / b; 18 | float discrim = fmaf(b, b, -4.f * a * c); 19 | 20 | // Check if the quadratic eq is solvable 21 | if (!linear_case && discrim < 0.f) 22 | return false; 23 | 24 | /* Numerically stable version of (-b (+/-) sqrt_discrim) / (2 * a) 25 | * 26 | * Based on the observation that one solution is always 27 | * accurate while the other is not. Finds the solution of 28 | * greater magnitude which does not suffer from loss of 29 | * precision and then uses the identity x1 * x2 = c / a 30 | */ 31 | float temp = -0.5f * (b + copysign(sqrt(discrim), b)); 32 | 33 | float x0p = temp / a, 34 | x1p = c / temp; 35 | 36 | // Order the results so that x0 < x1 37 | float x0m = min(x0p, x1p), 38 | x1m = max(x0p, x1p); 39 | 40 | x0 = (linear_case ? x0 : x0m); 41 | x1 = (linear_case ? x0 : x1m); 42 | 43 | return true; 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /include/mitsuba/render/optix/ray.cuh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef __CUDACC__ 3 | 4 | # include 5 | #include 6 | 7 | namespace optix { 8 | 9 | struct Ray3f { 10 | Vector3f o; /// Ray origin 11 | Vector3f d; /// Ray direction 12 | float mint; /// Minimum position on the ray segment 13 | float maxt; /// Maximum position on the ray segment 14 | float time; /// Time value associated with this ray 15 | 16 | DEVICE Ray3f() {} 17 | 18 | /// Construct a new ray (o, d) with bounds 19 | DEVICE Ray3f(const Vector3f &o, const Vector3f &d, float maxt, float time) 20 | : o(o), d(d), maxt(maxt), time(time) { } 21 | 22 | /// Return the position of a point along the ray 23 | DEVICE Vector3f operator() (float t) const { return fmaf(t, d, o); } 24 | }; 25 | 26 | /// Returns the current ray in instance space (based on the current instance transformation) 27 | DEVICE Ray3f get_ray() { 28 | Ray3f ray; 29 | ray.o = make_vector3f(optixGetObjectRayOrigin()); 30 | ray.d = make_vector3f(optixGetObjectRayDirection()); 31 | ray.mint = 0.0f; // Rays cast by Mitsuba always have mint==0. 32 | ray.maxt = optixGetRayTmax(); 33 | ray.time = optixGetRayTime(); 34 | return ray; 35 | } 36 | } // end namespace optix 37 | #endif // defined __CUDACC__ 38 | -------------------------------------------------------------------------------- /include/mitsuba/render/srgb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | NAMESPACE_BEGIN(mitsuba) 7 | 8 | template 9 | MI_INLINE Spectrum srgb_model_eval(const Array3f &coeff, 10 | const wavelength_t &wavelengths) { 11 | static_assert(!is_polarized_v, "srgb_model_eval(): requires unpolarized spectrum type!"); 12 | 13 | if constexpr (is_spectral_v) { 14 | Spectrum v = dr::fmadd(dr::fmadd(coeff.x(), wavelengths, coeff.y()), wavelengths, coeff.z()); 15 | 16 | return dr::select( 17 | dr::isinf(coeff.z()), dr::fmadd(dr::sign(coeff.z()), .5f, .5f), 18 | dr::maximum(0.f, dr::fmadd(.5f * v, dr::rsqrt(dr::fmadd(v, v, 1.f)), .5f)) 19 | ); 20 | } else { 21 | Throw("srgb_model_eval(): invoked for a non-spectral color type!"); 22 | } 23 | } 24 | 25 | template 26 | MI_INLINE dr::value_t srgb_model_mean(const Array3f &coeff) { 27 | using Float = dr::value_t; 28 | using Vec = dr::Array; 29 | 30 | Vec lambda = dr::linspace(MI_CIE_MIN, MI_CIE_MAX); 31 | Vec v = dr::fmadd(dr::fmadd(coeff.x(), lambda, coeff.y()), lambda, coeff.z()); 32 | Vec result = dr::select(dr::isinf(coeff.z()), dr::fmadd(dr::sign(coeff.z()), .5f, .5f), 33 | dr::maximum(0.f, dr::fmadd(.5f * v, dr::rsqrt(dr::fmadd(v, v, 1.f)), .5f))); 34 | return dr::mean(result); 35 | } 36 | 37 | /** 38 | * Look up the model coefficients for a sRGB color value 39 | * @param c An sRGB color value where all components are in [0, 1]. 40 | * @return Coefficients for use with \ref srgb_model_eval 41 | */ 42 | MI_EXPORT_LIB dr::Array srgb_model_fetch(const Color &); 43 | 44 | /// Sanity check: convert the coefficients back to sRGB 45 | // MI_EXPORT_LIB Color srgb_model_eval_rgb(const dr::Array &); 46 | 47 | NAMESPACE_END(mitsuba) 48 | -------------------------------------------------------------------------------- /include/mitsuba/ui/fwd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | NAMESPACE_BEGIN(mitsuba) 7 | 8 | namespace ng = nanogui; 9 | 10 | class MitsubaViewer; 11 | class GUITexture; 12 | 13 | NAMESPACE_END(mitsuba) 14 | -------------------------------------------------------------------------------- /include/mitsuba/ui/texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | NAMESPACE_BEGIN(mitsuba) 7 | 8 | /** 9 | * \brief Defines an abstraction for textures that works with 10 | * OpenGL, OpenGL ES, and Metal. 11 | * 12 | * Wraps nanogui::Texture and adds a new constructor for creating 13 | * textures from \ref mitsuba::Bitmap instances. 14 | */ 15 | class MI_EXPORT_UI GPUTexture : public nanogui::Texture { 16 | public: 17 | using Base = nanogui::Texture; 18 | using Base::Base; 19 | 20 | GPUTexture(const Bitmap *bitmap, 21 | InterpolationMode min_interpolation_mode = InterpolationMode::Bilinear, 22 | InterpolationMode mag_interpolation_mode = InterpolationMode::Bilinear, 23 | WrapMode wrap_mode = WrapMode::ClampToEdge); 24 | 25 | protected: 26 | virtual ~GPUTexture(); 27 | }; 28 | 29 | NAMESPACE_END(mitsuba) 30 | -------------------------------------------------------------------------------- /include/mitsuba/ui/viewer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | NAMESPACE_BEGIN(mitsuba) 7 | 8 | namespace ng = nanogui; 9 | 10 | /** 11 | * \brief Main class of the Mitsuba user interface 12 | */ 13 | class MI_EXPORT_UI MitsubaViewer : public ng::Screen { 14 | public: 15 | struct Tab; 16 | 17 | /// Create a new viewer interface 18 | MitsubaViewer(); 19 | 20 | /// Append an empty tab 21 | Tab *append_tab(const std::string &caption); 22 | 23 | /// Load content (a scene or an image) into a tab 24 | void load(Tab *tab, const fs::path &scene); 25 | 26 | using ng::Screen::perform_layout; 27 | virtual void perform_layout(NVGcontext* ctx) override; 28 | virtual bool keyboard_event(int key, int scancode, int action, int modifiers) override; 29 | 30 | protected: 31 | void close_tab_impl(Tab *tab); 32 | 33 | protected: 34 | ng::ref m_btn_play, m_btn_stop, m_btn_reload; 35 | ng::ref m_btn_menu, m_btn_settings; 36 | ng::ref m_contents, m_progress_panel; 37 | ng::ref m_progress_bar; 38 | ng::ref m_tab_widget; 39 | ng::ref m_view; 40 | std::vector m_tabs; 41 | }; 42 | 43 | NAMESPACE_END(mitsuba) 44 | -------------------------------------------------------------------------------- /resources/check-style.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Script to check include/test code for common code style errors. 4 | # 5 | # This script currently checks for 6 | # 7 | # 1. use of tabs instead of spaces 8 | # 2. trailing spaces 9 | # 3. missing space between keyword and parenthesis, e.g.: for(, if(, while( 10 | # 4. opening brace on its own line. It should always be on the same line as the 11 | # if/while/for/do statement. 12 | # 5. Missing space between right parenthesis and brace, e.g. 'for (...){' 13 | # 14 | # Invoke as: resources/check-style.sh 15 | # 16 | 17 | if [ -n "$BASH_VERSION" ]; then 18 | shopt -s globstar 19 | fi 20 | 21 | errors=0 22 | IFS=$'\n' 23 | found= 24 | # The mt=41 sets a red background for matched tabs: 25 | exec 3< <(GREP_COLORS='mt=41' grep $'\t' include/**/*.h src/**/*.{cpp,py} -rn --color=always) 26 | while read -u 3 f; do 27 | if [ -z "$found" ]; then 28 | echo -e '\e[31m\e[01mError: found tabs instead of spaces in the following files:\e[0m' 29 | found=1 30 | errors=1 31 | fi 32 | 33 | echo " $f" 34 | done 35 | 36 | found= 37 | # The mt=41 sets a red background for matched trailing spaces 38 | exec 3< <(GREP_COLORS='mt=41' grep '\s\+$' include/**/*.h src/**/*.{cpp,py} -rn --color=always) 39 | while read -u 3 f; do 40 | if [ -z "$found" ]; then 41 | echo -e '\e[31m\e[01mError: found trailing spaces in the following files:\e[0m' 42 | found=1 43 | errors=1 44 | fi 45 | 46 | echo " $f" 47 | done 48 | 49 | found= 50 | exec 3< <(GREP_COLORS='mt=41' grep '^\s*{\s*$' include/**/*.h src/**/*.cpp -rn --color=always) 51 | while read -u 3 f; do 52 | if [ -z "$found" ]; then 53 | echo -e '\e[31m\e[01mError: braces should occur on the same line as the if/while/.. statement. Found issues in the following files: \e[0m' 54 | found=1 55 | errors=1 56 | fi 57 | 58 | echo " $f" 59 | done 60 | 61 | found= 62 | exec 3< <(grep '\<\(if\|catch\|for\|while\)(\|){' include/**/*.h src/**/*.{cpp,py} -rn --color=always) 63 | while read -u 3 line; do 64 | if [ -z "$found" ]; then 65 | echo -e '\e[31m\e[01mError: found the following coding style problems:\e[0m' 66 | found=1 67 | errors=1 68 | fi 69 | 70 | echo " $line" 71 | done 72 | 73 | exit $errors 74 | -------------------------------------------------------------------------------- /resources/mitsuba-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/resources/mitsuba-logo.png -------------------------------------------------------------------------------- /resources/ptx/Makefile: -------------------------------------------------------------------------------- 1 | mts_include_folder = ../../include 2 | mts_shape_folder = ../../src/shapes/optix 3 | mts_optix_main = ../../src/render/optix/optix_rt.cu 4 | 5 | OPTIX_PATH := $(or $(OPTIX_PATH),/opt/optix/include/) 6 | 7 | all: optix_rt.ptx 8 | 9 | optix_rt.ptx: $(mts_optix_main) $(mts_include_folder)/* $(mts_shape_folder)/* 10 | nvcc $(mts_optix_main) \ 11 | -I $(mts_include_folder) -I $(mts_shape_folder) -I $(OPTIX_PATH) \ 12 | -O3 -gencode arch=compute_50,code=compute_50 --ptx 13 | 14 | clean: 15 | rm -f optix_rt.ptx 16 | -------------------------------------------------------------------------------- /resources/setpath.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM *************************************************************** 4 | REM * This script adds Mitsuba to the current path on Windows. 5 | REM * It assumes that Mitsuba is compiled within a subdirectory 6 | REM * named 'build'. 7 | REM *************************************************************** 8 | 9 | set MITSUBA_DIR=%~dp0 10 | set PATH=%MITSUBA_DIR%;%PATH% 11 | set PYTHONPATH=%MITSUBA_DIR%/python;%PYTHONPATH% 12 | -------------------------------------------------------------------------------- /resources/setpath.ps1: -------------------------------------------------------------------------------- 1 | # *************************************************************** 2 | # * This script adds Mitsuba to the current path on Windows. 3 | # * It assumes that Mitsuba is compiled within a subdirectory 4 | # * named 'build'. 5 | # *************************************************************** 6 | 7 | $env:MITSUBA_DIR = $PSScriptRoot 8 | $env:PATH = $env:MITSUBA_DIR + ";" + $env:PATH 9 | $env:PYTHONPATH = $env:MITSUBA_DIR + "\python" + ";" + $env:PYTHONPATH -------------------------------------------------------------------------------- /resources/setpath.sh: -------------------------------------------------------------------------------- 1 | # This script adds Mitsuba and the Python bindings to the shell's search 2 | # path. It must be executed via the 'source' command so that it can modify 3 | # the relevant environment variables. 4 | 5 | MI_DIR="" 6 | MI_VARIANTS="@MI_VARIANT_NAMES_STR@" 7 | 8 | if [ "$BASH_VERSION" ]; then 9 | if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then 10 | MI_DIR=$(dirname "$BASH_SOURCE") 11 | MI_DIR=$(builtin cd "$MI_DIR"; builtin pwd) 12 | fi 13 | elif [ "$ZSH_VERSION" ]; then 14 | if [[ -n ${(M)zsh_eval_context:#file} ]]; then 15 | MI_DIR=$(dirname "$0:A") 16 | fi 17 | fi 18 | 19 | if [ -z "$MI_DIR" ]; then 20 | echo "This script must be executed via the 'source' command, i.e.:" 21 | echo "$ source ${0}" 22 | exit 0 23 | fi 24 | 25 | export PYTHONPATH="$MI_DIR/python:$PYTHONPATH" 26 | export PATH="$MI_DIR:$PATH" 27 | 28 | if [ "$BASH_VERSION" ]; then 29 | _mitsuba() { 30 | local FLAGS 31 | FLAGS="-a -h -m -o -s -t -u -v -D" 32 | 33 | if [[ $2 == -* ]]; then 34 | COMPREPLY=( $(compgen -W "${FLAGS}" -- $2) ) 35 | elif [[ $3 == -m ]]; then 36 | COMPREPLY=( $(compgen -W "${MI_VARIANTS}" -- $2) ) 37 | else 38 | COMPREPLY=() 39 | fi 40 | 41 | return 0 42 | } 43 | complete -F _mitsuba -o default mitsuba 44 | elif [ "$ZSH_VERSION" ]; then 45 | _mitsuba() { 46 | _arguments \ 47 | '-a[Add an entry to the search path]' \ 48 | '-h[Help]' \ 49 | '-m[Select the variant/mode (e.g. "llvm_rgb")]' \ 50 | '-o[Specify the output filename]' \ 51 | '-s[Sensor/camera index to use for rendering]' \ 52 | '-t[Override the number of threads]' \ 53 | '-u[Update the scene description to the latest version]' \ 54 | '-v[Be more verbose. Can be specified multiple times]' \ 55 | '-D[Define a constant that can be referenced in the scene]' \ 56 | '*: :->file' 57 | 58 | if [[ $state == '-' ]]; then 59 | : 60 | elif [[ $words[CURRENT-1] == '-m' ]]; then 61 | compadd "$@" ${=MI_VARIANTS} 62 | else 63 | _files 64 | fi 65 | 66 | } 67 | compdef _mitsuba mitsuba 68 | fi 69 | -------------------------------------------------------------------------------- /resources/variant-stub.cmake: -------------------------------------------------------------------------------- 1 | # Syntax: cmake -P variant-stub.cmake 2 | # Applies post-cleanup of generated Mitsuba Python stubs 3 | 4 | file(READ ${CMAKE_ARGV3} FILE_CONTENTS) 5 | string(REPLACE "drjit.llvm" "drjit.auto" FILE_CONTENTS "${FILE_CONTENTS}") 6 | string(REPLACE "drjit.cuda" "drjit.auto" FILE_CONTENTS "${FILE_CONTENTS}") 7 | string(REPLACE "types.CapsuleType" "object" FILE_CONTENTS "${FILE_CONTENTS}") 8 | string(REPLACE "filesystem.path" "str" FILE_CONTENTS "${FILE_CONTENTS}") 9 | string(REPLACE "scalar_rgb." "" FILE_CONTENTS "${FILE_CONTENTS}") 10 | string(REPLACE "class Properties(_Properties)" "class Properties" FILE_CONTENTS "${FILE_CONTENTS}") 11 | string(REPLACE "_Properties" "Properties" FILE_CONTENTS "${FILE_CONTENTS}") 12 | string(REGEX REPLACE "[\n ]+python\\.[^-\\s]+ as python\\.[^-\\s]+," "" FILE_CONTENTS "${FILE_CONTENTS}") 13 | string(REGEX REPLACE "import mitsuba as [a-z_]+" "" FILE_CONTENTS "${FILE_CONTENTS}") 14 | 15 | file(WRITE "${CMAKE_ARGV4}" "${FILE_CONTENTS}") -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------- 2 | # Mitsuba library 3 | # ---------------------------------------------------------- 4 | 5 | include_directories( 6 | ${CMAKE_CURRENT_SOURCE_DIR}/../ext/nanobind/ext/robin_map/include 7 | ) 8 | 9 | add_subdirectory(core) 10 | add_subdirectory(render) 11 | 12 | add_library(mitsuba SHARED) 13 | target_link_libraries(mitsuba PUBLIC mitsuba-core) 14 | target_link_libraries(mitsuba PUBLIC mitsuba-render) 15 | set_target_properties(mitsuba PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON) 16 | 17 | if (CMAKE_CXX_COMPILER_ID MATCHES "^(GNU)$") 18 | # Treat undefined symbols as errors when linking 19 | target_link_libraries(mitsuba PRIVATE -Wl,--no-undefined) 20 | # Silence an incorrect warning message in GCC LTO builds 21 | target_link_options(mitsuba PRIVATE -Walloc-size-larger-than=18446744073709551615) 22 | endif() 23 | 24 | # ---------------------------------------------------------- 25 | # Mitsuba executable 26 | # ---------------------------------------------------------- 27 | 28 | add_subdirectory(mitsuba) 29 | 30 | # ---------------------------------------------------------- 31 | # Plugins 32 | # ---------------------------------------------------------- 33 | 34 | add_subdirectory(bsdfs) 35 | add_subdirectory(emitters) 36 | add_subdirectory(films) 37 | add_subdirectory(integrators) 38 | add_subdirectory(media) 39 | add_subdirectory(phase) 40 | add_subdirectory(rfilters) 41 | add_subdirectory(samplers) 42 | add_subdirectory(sensors) 43 | add_subdirectory(shapes) 44 | add_subdirectory(spectra) 45 | add_subdirectory(textures) 46 | add_subdirectory(volumes) 47 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 48 | 49 | # ---------------------------------------------------------- 50 | # Python bindings and extensions 51 | # ---------------------------------------------------------- 52 | 53 | if (MI_ENABLE_PYTHON) 54 | add_subdirectory(python) 55 | endif() 56 | -------------------------------------------------------------------------------- /src/bsdfs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "bsdfs") 2 | 3 | add_plugin(blendbsdf blendbsdf.cpp) 4 | add_plugin(bumpmap bumpmap.cpp) 5 | add_plugin(conductor conductor.cpp) 6 | add_plugin(dielectric dielectric.cpp) 7 | add_plugin(diffuse diffuse.cpp) 8 | add_plugin(hair hair.cpp) 9 | add_plugin(mask mask.cpp) 10 | add_plugin(measured measured.cpp) 11 | add_plugin(normalmap normalmap.cpp) 12 | add_plugin(null null.cpp) 13 | add_plugin(plastic plastic.cpp) 14 | add_plugin(roughconductor roughconductor.cpp) 15 | add_plugin(roughdielectric roughdielectric.cpp) 16 | add_plugin(roughplastic roughplastic.cpp) 17 | add_plugin(thindielectric thindielectric.cpp) 18 | add_plugin(twosided twosided.cpp) 19 | add_plugin(polarizer polarizer.cpp) 20 | add_plugin(retarder retarder.cpp) 21 | add_plugin(circular circular.cpp) 22 | add_plugin(measured_polarized measured_polarized.cpp) 23 | add_plugin(pplastic pplastic.cpp) 24 | add_plugin(principled principled.cpp) 25 | add_plugin(principledthin principledthin.cpp) 26 | 27 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 28 | -------------------------------------------------------------------------------- /src/bsdfs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/bsdfs/__init__.py -------------------------------------------------------------------------------- /src/bsdfs/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/bsdfs/tests/__init__.py -------------------------------------------------------------------------------- /src/bsdfs/tests/test_diffuse.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | def test01_create(variant_scalar_rgb): 6 | b = mi.load_dict({'type': 'diffuse'}) 7 | assert b is not None 8 | assert b.component_count() == 1 9 | assert b.flags(0) == mi.BSDFFlags.DiffuseReflection | mi.BSDFFlags.FrontSide 10 | assert b.flags() == b.flags(0) 11 | 12 | 13 | def test02_eval_pdf(variant_scalar_rgb): 14 | bsdf = mi.load_dict({'type': 'diffuse'}) 15 | 16 | si = mi.SurfaceInteraction3f() 17 | si.p = [0, 0, 0] 18 | si.n = [0, 0, 1] 19 | si.wi = [0, 0, 1] 20 | si.sh_frame = mi.Frame3f(si.n) 21 | 22 | ctx = mi.BSDFContext() 23 | 24 | for i in range(20): 25 | theta = i / 19.0 * (dr.pi / 2) 26 | wo = [dr.sin(theta), 0, dr.cos(theta)] 27 | 28 | v_pdf = bsdf.pdf(ctx, si, wo=wo) 29 | v_eval = bsdf.eval(ctx, si, wo=wo)[0] 30 | assert dr.allclose(v_pdf, wo[2] / dr.pi) 31 | assert dr.allclose(v_eval, 0.5 * wo[2] / dr.pi) 32 | 33 | v_eval_pdf = bsdf.eval_pdf(ctx, si, wo=wo) 34 | assert dr.allclose(v_eval, v_eval_pdf[0]) 35 | assert dr.allclose(v_pdf, v_eval_pdf[1]) 36 | 37 | 38 | def test03_chi2(variants_vec_backends_once_rgb): 39 | from mitsuba.chi2 import BSDFAdapter, ChiSquareTest, SphericalDomain 40 | 41 | sample_func, pdf_func = BSDFAdapter("diffuse", '') 42 | 43 | chi2 = ChiSquareTest( 44 | domain=SphericalDomain(), 45 | sample_func=sample_func, 46 | pdf_func=pdf_func, 47 | sample_dim=3 48 | ) 49 | 50 | assert chi2.run() 51 | -------------------------------------------------------------------------------- /src/bsdfs/tests/test_measured_polarized.py: -------------------------------------------------------------------------------- 1 | import mitsuba 2 | import pytest 3 | import drjit as dr 4 | import mitsuba as mi 5 | import numpy as np 6 | 7 | from mitsuba.scalar_rgb.test.util import fresolver_append_path 8 | 9 | @fresolver_append_path 10 | def test01_evaluation(variant_scalar_spectral_polarized): 11 | # Here we load a small example pBSDF file and evaluate the BSDF for a fixed 12 | # incident and outgoing position. Any future changes to polarization frames 13 | # or table interpolation should keep the values below unchanged. 14 | # 15 | # For convenience, we use a file where the usual resolution of parameters 16 | # (phi_d x theta_d x theta_h x wavelengths x Mueller mat. ) was significantly 17 | # downsampled from (361 x 91 x 91 x 5 x 4 x 4) to (22 x 9 x 9 x 5 x 4 x 4). 18 | 19 | bsdf = mi.load_dict({ 20 | 'type': 'measured_polarized', 21 | 'filename': 'resources/data/tests/pbsdf/spectralon_lowres.pbsdf' 22 | }) 23 | 24 | phi_i = 30 * dr.pi / 180.0 25 | theta_i = 10 * dr.pi / 180.0 26 | wi = mi.Vector3f([dr.sin(theta_i) * dr.cos(phi_i), 27 | dr.sin(theta_i) * dr.sin(phi_i), 28 | dr.cos(theta_i)]) 29 | 30 | ctx = mi.BSDFContext() 31 | si = mi.SurfaceInteraction3f() 32 | si.p = [0, 0, 0] 33 | si.wi = wi 34 | si.sh_frame = mi.Frame3f([0, 0, 1]) 35 | si.wavelengths = [500, 500, 500, 500] 36 | 37 | phi_o = 200 * dr.pi / 180.0 38 | theta_o = 3 * dr.pi / 180.0 39 | wi = mi.Vector3f([dr.sin(theta_o) * dr.cos(phi_o), 40 | dr.sin(theta_o) * dr.sin(phi_o), 41 | dr.cos(theta_o)]) 42 | 43 | value = bsdf.eval(ctx, si, wi) 44 | 45 | # Manually extract matrix for first wavelength 46 | # TODO: An issue with ArrayBase empty base class optimization 47 | # This means that the data isn't tightly packed 48 | mtx = mi.ScalarMatrix4f() 49 | for i in range(0,4): 50 | for j in range(0,4): 51 | mtx[i, j] = value[i,j][0] 52 | 53 | ref = [[ 0.17119151, -0.00223141, 0.00754681, 0.00010021], 54 | [-0.00393003, 0.00427623, -0.00117126, -0.00310079], 55 | [-0.00424358, 0.00312945, -0.01219576, 0.00086167], 56 | [ 0.00099006, -0.00345963, -0.00285343, -0.00205485]] 57 | 58 | assert dr.allclose(ref, mtx) 59 | -------------------------------------------------------------------------------- /src/bsdfs/tests/test_pplastic.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mitsuba as mi 3 | 4 | 5 | @pytest.mark.slow 6 | def test01_chi2_smooth(variants_vec_backends_once_rgb): 7 | xml = """ 8 | 9 | """ 10 | sample_func, pdf_func = mi.chi2.BSDFAdapter("pplastic", xml) 11 | 12 | chi2 = mi.chi2.ChiSquareTest( 13 | domain=mi.chi2.SphericalDomain(), 14 | sample_func=sample_func, 15 | pdf_func=pdf_func, 16 | sample_dim=3, 17 | res=201, 18 | ires=16, 19 | seed=2 20 | ) 21 | 22 | assert chi2.run() 23 | 24 | 25 | @pytest.mark.slow 26 | def test02_chi2_rough(variants_vec_backends_once_rgb): 27 | xml = """ 28 | 29 | """ 30 | sample_func, pdf_func = mi.chi2.BSDFAdapter("pplastic", xml) 31 | 32 | chi2 = mi.chi2.ChiSquareTest( 33 | domain=mi.chi2.SphericalDomain(), 34 | sample_func=sample_func, 35 | pdf_func=pdf_func, 36 | sample_dim=3, 37 | ) 38 | 39 | assert chi2.run() 40 | -------------------------------------------------------------------------------- /src/cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | find_program(SPHINX_EXECUTABLE NAMES sphinx-build 2 | HINTS 3 | $ENV{SPHINX_DIR} 4 | PATH_SUFFIXES bin 5 | DOC "Sphinx documentation generator" 6 | ) 7 | 8 | include(FindPackageHandleStandardArgs) 9 | 10 | find_package_handle_standard_args(Sphinx DEFAULT_MSG 11 | SPHINX_EXECUTABLE 12 | ) 13 | 14 | mark_as_advanced(SPHINX_EXECUTABLE) 15 | -------------------------------------------------------------------------------- /src/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/core/__init__.py -------------------------------------------------------------------------------- /src/core/dstream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | NAMESPACE_BEGIN(mitsuba) 4 | 5 | DummyStream::DummyStream() 6 | : Stream(), m_size(0), m_pos(0), m_is_closed(false) { } 7 | 8 | void DummyStream::close() { m_is_closed = true; }; 9 | 10 | bool DummyStream::is_closed() const { return m_is_closed; }; 11 | 12 | void DummyStream::read(void *, size_t) { 13 | /// Always throws, since DummyStream is write-only. 14 | Throw("DummyStream does not support reading."); 15 | } 16 | 17 | void DummyStream::write(const void *, size_t size) { 18 | /// Does not actually write anything, only updates the stream's position and size. 19 | if (is_closed()) 20 | Throw("Attempted to write to a closed stream: %s", to_string()); 21 | 22 | m_size = std::max(m_size, m_pos + size); 23 | m_pos += size; 24 | } 25 | 26 | void DummyStream::seek(size_t pos) { 27 | /* Updates the current position in the stream. 28 | Even though the DummyStream doesn't write anywhere, position is 29 | taken into account to accurately compute the size of the stream. */ 30 | m_pos = pos; 31 | } 32 | 33 | void DummyStream::truncate(size_t size) { 34 | /* Simply sets the current size of the stream. 35 | The position is updated to min(old_position, size). */ 36 | m_size = size; // No underlying data, so there's nothing else to do. 37 | m_pos = std::min(m_pos, size); 38 | } 39 | 40 | size_t DummyStream::tell() const { return m_pos; } 41 | size_t DummyStream::size() const { return m_size; } 42 | void DummyStream::flush() { /* Nothing to do */ } 43 | bool DummyStream::can_write() const { return !is_closed(); } 44 | bool DummyStream::can_read() const { return false; } 45 | 46 | MI_IMPLEMENT_CLASS(DummyStream, Stream) 47 | 48 | NAMESPACE_END(mitsuba) 49 | -------------------------------------------------------------------------------- /src/core/formatter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | NAMESPACE_BEGIN(mitsuba) 8 | 9 | DefaultFormatter::DefaultFormatter() 10 | : m_has_date(true), m_has_log_level(true), m_has_thread(true), 11 | m_has_class(true) { } 12 | 13 | std::string DefaultFormatter::format(mitsuba::LogLevel level, const Class *class_, 14 | const Thread *thread, const char *file, int line, 15 | const std::string &msg) { 16 | std::ostringstream oss; 17 | std::istringstream iss(msg); 18 | char buffer[128]; 19 | std::string msg_line; 20 | time_t time_ = std::time(nullptr); 21 | strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S ", std::localtime(&time_)); 22 | int line_idx = 0; 23 | 24 | while (std::getline(iss, msg_line) || line_idx == 0) { 25 | if (line_idx > 0) 26 | oss << '\n'; 27 | 28 | /* Date/Time */ 29 | if (m_has_date) 30 | oss << buffer; 31 | 32 | /* Log level */ 33 | if (m_has_log_level) { 34 | switch (level) { 35 | case Trace: oss << "TRACE "; break; 36 | case Debug: oss << "DEBUG "; break; 37 | case Info: oss << "INFO "; break; 38 | case Warn: oss << "WARN "; break; 39 | case Error: oss << "ERROR "; break; 40 | default: oss << "CUSTM "; break; 41 | } 42 | } 43 | 44 | /* Thread */ 45 | if (thread && m_has_thread) { 46 | oss << thread->name(); 47 | 48 | for (int i=0; i<(6 - (int) thread->name().size()); i++) 49 | oss << ' '; 50 | } 51 | 52 | /* Class */ 53 | if (m_has_class) { 54 | if (class_) 55 | oss << "[" << class_->name() << "] "; 56 | else if (line != -1 && file) 57 | oss << "[" << fs::path(file).filename() << ":" << line << "] "; 58 | } 59 | 60 | /* Message */ 61 | oss << msg_line; 62 | line_idx++; 63 | } 64 | 65 | return oss.str(); 66 | } 67 | 68 | MI_IMPLEMENT_CLASS(Formatter, Object) 69 | MI_IMPLEMENT_CLASS(DefaultFormatter, Formatter) 70 | 71 | NAMESPACE_END(mitsuba) 72 | -------------------------------------------------------------------------------- /src/core/fresolver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | NAMESPACE_BEGIN(mitsuba) 6 | 7 | FileResolver::FileResolver() : Object() { 8 | m_paths.push_back(fs::current_path()); 9 | } 10 | 11 | FileResolver::FileResolver(const FileResolver &fr) 12 | : Object(), m_paths(fr.m_paths) { } 13 | 14 | void FileResolver::erase(const fs::path &p) { 15 | m_paths.erase(std::remove(m_paths.begin(), m_paths.end(), p), m_paths.end()); 16 | } 17 | 18 | bool FileResolver::contains(const fs::path &p) const { 19 | return std::find(m_paths.begin(), m_paths.end(), p) != m_paths.end(); 20 | } 21 | 22 | fs::path FileResolver::resolve(const fs::path &path) const { 23 | if (!path.is_absolute()) { 24 | for (auto const &base : m_paths) { 25 | fs::path combined = base / path; 26 | if (fs::exists(combined)) 27 | return combined; 28 | } 29 | } 30 | return path; 31 | } 32 | 33 | std::string FileResolver::to_string() const { 34 | std::ostringstream oss; 35 | oss << "FileResolver[" << std::endl; 36 | for (size_t i = 0; i < m_paths.size(); ++i) { 37 | oss << " \"" << m_paths[i] << "\""; 38 | if (i + 1 < m_paths.size()) 39 | oss << ","; 40 | oss << std::endl; 41 | } 42 | oss << "]"; 43 | return oss.str(); 44 | } 45 | 46 | MI_IMPLEMENT_CLASS(FileResolver, Object) 47 | 48 | NAMESPACE_END(mitsuba) 49 | -------------------------------------------------------------------------------- /src/core/jit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | NAMESPACE_BEGIN(mitsuba) 5 | 6 | static Jit *jit = nullptr; 7 | 8 | Jit::Jit() { } 9 | Jit *Jit::get_instance() { return jit; } 10 | 11 | void Jit::static_initialization() { 12 | #if defined(DRJIT_X86_64) 13 | jit = new Jit(); 14 | #endif 15 | } 16 | 17 | void Jit::static_shutdown() { 18 | #if defined(DRJIT_X86_64) 19 | delete jit; 20 | jit = nullptr; 21 | #endif 22 | } 23 | 24 | NAMESPACE_END(mitsuba) 25 | -------------------------------------------------------------------------------- /src/core/object.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | NAMESPACE_BEGIN(mitsuba) 8 | 9 | std::vector> Object::expand() const { return { }; } 10 | 11 | void Object::traverse(TraversalCallback * /*callback*/) { } 12 | 13 | void Object::parameters_changed(const std::vector &/*keys*/) { } 14 | 15 | std::string Object::id() const { return std::string(); } 16 | 17 | void Object::set_id(const std::string&/*id*/) { } 18 | 19 | std::string Object::to_string() const { 20 | std::ostringstream oss; 21 | oss << class_()->name() << "[" << (void *) this << "]"; 22 | return oss.str(); 23 | } 24 | 25 | std::ostream& operator<<(std::ostream &os, const Object *object) { 26 | os << ((object != nullptr) ? object->to_string() : "nullptr"); 27 | return os; 28 | } 29 | 30 | MI_IMPLEMENT_CLASS(Object,) 31 | NAMESPACE_END(mitsuba) 32 | -------------------------------------------------------------------------------- /src/core/profiler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | NAMESPACE_BEGIN(mitsuba) 6 | 7 | #if defined(MI_ENABLE_ITTNOTIFY) 8 | __itt_domain *mitsuba_itt_domain = nullptr; 9 | __itt_string_handle * 10 | mitsuba_itt_phase[int(ProfilerPhase::ProfilerPhaseCount)] { }; 11 | #endif 12 | 13 | void Profiler::static_initialization() { 14 | #if defined(MI_ENABLE_ITTNOTIFY) 15 | mitsuba_itt_domain = __itt_domain_create("mitsuba"); 16 | for (int i = 0; i < (int) ProfilerPhase::ProfilerPhaseCount; ++i) 17 | mitsuba_itt_phase[i] = __itt_string_handle_create(profiler_phase_id[i]); 18 | #endif 19 | } 20 | 21 | void Profiler::static_shutdown() { } 22 | 23 | NAMESPACE_END(mitsuba) 24 | -------------------------------------------------------------------------------- /src/core/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CORE_PY_V_SRC 2 | ${CMAKE_CURRENT_SOURCE_DIR}/drjit_v.cpp 3 | ${CMAKE_CURRENT_SOURCE_DIR}/bbox_v.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/bsphere_v.cpp 5 | ${CMAKE_CURRENT_SOURCE_DIR}/distr_1d_v.cpp 6 | ${CMAKE_CURRENT_SOURCE_DIR}/distr_2d_v.cpp 7 | ${CMAKE_CURRENT_SOURCE_DIR}/frame_v.cpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/math_v.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/object_v.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/qmc_v.cpp 11 | ${CMAKE_CURRENT_SOURCE_DIR}/properties_v.cpp 12 | ${CMAKE_CURRENT_SOURCE_DIR}/random_v.cpp 13 | ${CMAKE_CURRENT_SOURCE_DIR}/ray_v.cpp 14 | ${CMAKE_CURRENT_SOURCE_DIR}/rfilter_v.cpp 15 | ${CMAKE_CURRENT_SOURCE_DIR}/spectrum_v.cpp 16 | ${CMAKE_CURRENT_SOURCE_DIR}/spline_v.cpp 17 | ${CMAKE_CURRENT_SOURCE_DIR}/transform_v.cpp 18 | ${CMAKE_CURRENT_SOURCE_DIR}/vector_v.cpp 19 | ${CMAKE_CURRENT_SOURCE_DIR}/warp_v.cpp 20 | ${CMAKE_CURRENT_SOURCE_DIR}/xml_v.cpp 21 | ${CMAKE_CURRENT_SOURCE_DIR}/quad_v.cpp 22 | PARENT_SCOPE 23 | ) 24 | 25 | set(CORE_PY_SRC 26 | ${CMAKE_CURRENT_SOURCE_DIR}/atomic.cpp 27 | ${CMAKE_CURRENT_SOURCE_DIR}/appender.cpp 28 | ${CMAKE_CURRENT_SOURCE_DIR}/argparser.cpp 29 | ${CMAKE_CURRENT_SOURCE_DIR}/bitmap.cpp 30 | ${CMAKE_CURRENT_SOURCE_DIR}/cast.cpp 31 | ${CMAKE_CURRENT_SOURCE_DIR}/filesystem.cpp 32 | ${CMAKE_CURRENT_SOURCE_DIR}/formatter.cpp 33 | ${CMAKE_CURRENT_SOURCE_DIR}/fresolver.cpp 34 | ${CMAKE_CURRENT_SOURCE_DIR}/logger.cpp 35 | ${CMAKE_CURRENT_SOURCE_DIR}/misc.cpp 36 | ${CMAKE_CURRENT_SOURCE_DIR}/mmap.cpp 37 | ${CMAKE_CURRENT_SOURCE_DIR}/object.cpp 38 | ${CMAKE_CURRENT_SOURCE_DIR}/progress.cpp 39 | ${CMAKE_CURRENT_SOURCE_DIR}/rfilter.cpp 40 | ${CMAKE_CURRENT_SOURCE_DIR}/stream.cpp 41 | ${CMAKE_CURRENT_SOURCE_DIR}/struct.cpp 42 | ${CMAKE_CURRENT_SOURCE_DIR}/thread.cpp 43 | ${CMAKE_CURRENT_SOURCE_DIR}/timer.cpp 44 | PARENT_SCOPE 45 | ) 46 | -------------------------------------------------------------------------------- /src/core/python/appender.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Trampoline for derived types implemented in Python 8 | class PyAppender : public Appender { 9 | public: 10 | NB_TRAMPOLINE(Appender, 2); 11 | 12 | void append(mitsuba::LogLevel level, const std::string &text) override { 13 | NB_OVERRIDE_PURE(append, level, text); 14 | } 15 | 16 | void log_progress(float progress, const std::string &name, 17 | const std::string &formatted, 18 | const std::string &eta, const void *ptr) override { 19 | NB_OVERRIDE_PURE(log_progress, progress, name, formatted, eta, (void*)ptr); 20 | } 21 | }; 22 | 23 | MI_PY_EXPORT(Appender) { 24 | nb::enum_(m, "LogLevel", nb::is_arithmetic(), D(LogLevel)) 25 | .value("Trace", Trace, D(LogLevel, Trace)) 26 | .value("Debug", Debug, D(LogLevel, Debug)) 27 | .value("Info", Info, D(LogLevel, Info)) 28 | .value("Warn", Warn, D(LogLevel, Warn)) 29 | .value("Error", Error, D(LogLevel, Error)); 30 | 31 | MI_PY_TRAMPOLINE_CLASS(PyAppender, Appender, Object) 32 | .def(nb::init<>()) 33 | .def_method(Appender, append, "level"_a, "text"_a) 34 | .def_method(Appender, log_progress, "progress"_a, "name"_a, 35 | "formatted"_a, "eta"_a, "ptr"_a = nb::none()); 36 | 37 | MI_PY_CLASS(StreamAppender, Appender) 38 | .def(nb::init(), D(StreamAppender, StreamAppender)) 39 | .def_method(StreamAppender, logs_to_file) 40 | .def_method(StreamAppender, read_log); 41 | } 42 | -------------------------------------------------------------------------------- /src/core/python/argparser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | MI_PY_EXPORT(ArgParser) { 10 | nb::class_ argp(m, "ArgParser", D(ArgParser)); 11 | nb::class_ argpa(argp, "Arg", D(ArgParser, Arg)); 12 | 13 | argp.def(nb::init<>()) 14 | .def("add", (const ArgParser::Arg * (ArgParser::*) (const std::string &, bool)) 15 | &ArgParser::add, "prefix"_a, "extra"_a = false, 16 | nb::rv_policy::reference_internal, 17 | D(ArgParser, add, 2)) 18 | .def("add", (const ArgParser::Arg * (ArgParser::*) (const std::vector &, bool)) 19 | &ArgParser::add, "prefixes"_a, "extra"_a = false, 20 | nb::rv_policy::reference_internal, 21 | D(ArgParser, add)) 22 | .def("parse", [](ArgParser &a, std::vector args) { 23 | std::unique_ptr args_(new const char *[args.size()]); 24 | for (size_t i = 0; i 2 | #include 3 | 4 | MI_PY_EXPORT(atomic) { 5 | using AtomicFloat = mitsuba::AtomicFloat<>; 6 | 7 | nb::class_(m, "AtomicFloat", D(AtomicFloat)) 8 | .def(nb::init(), D(AtomicFloat, AtomicFloat)) 9 | .def(nb::self += float(), D(AtomicFloat, operator, iadd), nb::rv_policy::none) 10 | .def(nb::self -= float(), D(AtomicFloat, operator, isub), nb::rv_policy::none) 11 | .def(nb::self *= float(), D(AtomicFloat, operator, imul), nb::rv_policy::none) 12 | .def(nb::self /= float(), D(AtomicFloat, operator, idiv), nb::rv_policy::none) 13 | .def("__float__", [](const AtomicFloat &af) { return (float) af; }, 14 | D(AtomicFloat, operator, T0)); 15 | } 16 | -------------------------------------------------------------------------------- /src/core/python/bsphere_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | template auto bind_bsphere(nb::module_ &m, const char *name) { 9 | using Point = typename BSphere::Point; 10 | using Float = typename BSphere::Float; 11 | 12 | MI_PY_CHECK_ALIAS(BSphere, name) { 13 | nb::class_(m, name, D(BoundingSphere)) 14 | .def(nb::init<>(), D(BoundingSphere, BoundingSphere)) 15 | .def(nb::init(), D(BoundingSphere, BoundingSphere, 2)) 16 | .def(nb::init()) 17 | .def("empty", &BSphere::empty, D(BoundingSphere, empty)) 18 | .def("contains", 19 | [](const BSphere &self, const Point &p, bool strict) { 20 | return strict ? self.template contains(p) 21 | : self.template contains(p); 22 | }, D(BoundingSphere, contains), "p"_a, "strict"_a = false) 23 | .def("expand", &BSphere::expand, D(BoundingSphere, expand)) 24 | .def("ray_intersect", 25 | [](const BSphere &self, const Ray &ray) { 26 | return self.ray_intersect(ray); 27 | }, D(BoundingSphere, ray_intersect), "ray"_a) 28 | .def(nb::self == nb::self) 29 | .def(nb::self != nb::self) 30 | .def_rw("center", &BSphere::center) 31 | .def_rw("radius", &BSphere::radius) 32 | .def_repr(BSphere); 33 | } 34 | } 35 | 36 | MI_PY_EXPORT(BoundingSphere) { 37 | MI_PY_IMPORT_TYPES() 38 | 39 | bind_bsphere(m, "BoundingSphere3f"); 40 | 41 | if constexpr (!std::is_same_v) 42 | bind_bsphere(m, "ScalarBoundingSphere3f"); 43 | } 44 | -------------------------------------------------------------------------------- /src/core/python/cast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using Caster = nb::object(*)(mitsuba::Object *); 5 | 6 | static std::vector casters; 7 | 8 | nb::object cast_object(Object *o) { 9 | for (auto &caster : casters) { 10 | nb::object po = ((Caster) caster)(o); 11 | if (po) 12 | return po; 13 | } 14 | return nb::cast(o); 15 | } 16 | 17 | MI_PY_EXPORT(Cast) { 18 | m.attr("casters") = nb::cast((void *) &casters); 19 | m.attr("cast_object") = nb::cast((void *) &cast_object); 20 | } 21 | -------------------------------------------------------------------------------- /src/core/python/formatter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | // Trampoline for derived types implemented in Python 9 | class PyFormatter : public Formatter { 10 | public: 11 | NB_TRAMPOLINE(Formatter, 1); 12 | 13 | std::string format(mitsuba::LogLevel level, const Class *class_, 14 | const Thread *thread, const char *file, int line, 15 | const std::string &msg) override { 16 | NB_OVERRIDE_PURE(format, level, class_, thread, file, line, msg); 17 | } 18 | }; 19 | 20 | MI_PY_EXPORT(Formatter) { 21 | MI_PY_TRAMPOLINE_CLASS(PyFormatter, Formatter, Object) 22 | .def(nb::init<>()) 23 | .def_method(Formatter, format, "level"_a, "class_"_a, 24 | "thread"_a, "file"_a, "line"_a, 25 | "msg"_a); 26 | 27 | MI_PY_CLASS(DefaultFormatter, Formatter) 28 | .def(nb::init<>()) 29 | .def_method(DefaultFormatter, has_date) 30 | .def_method(DefaultFormatter, set_has_date) 31 | .def_method(DefaultFormatter, has_thread) 32 | .def_method(DefaultFormatter, set_has_thread) 33 | .def_method(DefaultFormatter, has_log_level) 34 | .def_method(DefaultFormatter, set_has_log_level) 35 | .def_method(DefaultFormatter, has_class) 36 | .def_method(DefaultFormatter, set_has_class); 37 | } 38 | -------------------------------------------------------------------------------- /src/core/python/frame_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | MI_PY_EXPORT(Frame) { 8 | MI_PY_IMPORT_TYPES() 9 | MI_PY_CHECK_ALIAS(Frame3f, "Frame3f") { 10 | auto f = nb::class_(m, "Frame3f", D(Frame)) 11 | .def(nb::init<>(), D(Frame, Frame)) 12 | .def(nb::init(), "Copy constructor") 13 | .def(nb::init(), D(Frame, Frame, 3)) 14 | .def(nb::init(), D(Frame, Frame, 4)) 15 | .def(nb::self == nb::self, D(Frame, operator_eq)) 16 | .def(nb::self != nb::self, D(Frame, operator_ne)) 17 | .def("to_local", &Frame3f::to_local, "v"_a, D(Frame, to_local)) 18 | .def("to_world", &Frame3f::to_world, "v"_a, D(Frame, to_world)) 19 | .def_static("cos_theta", &Frame3f::cos_theta, "v"_a, D(Frame, cos_theta)) 20 | .def_static("cos_theta_2", &Frame3f::cos_theta_2, "v"_a, D(Frame, cos_theta_2)) 21 | .def_static("sin_theta", &Frame3f::sin_theta, "v"_a, D(Frame, sin_theta)) 22 | .def_static("sin_theta_2", &Frame3f::sin_theta_2, "v"_a, D(Frame, sin_theta_2)) 23 | .def_static("tan_theta", &Frame3f::tan_theta, "v"_a, D(Frame, tan_theta)) 24 | .def_static("tan_theta_2", &Frame3f::tan_theta_2, "v"_a, D(Frame, tan_theta_2)) 25 | .def_static("sin_phi", &Frame3f::sin_phi, "v"_a, D(Frame, sin_phi)) 26 | .def_static("sin_phi_2", &Frame3f::sin_phi_2, "v"_a, D(Frame, sin_phi_2)) 27 | .def_static("cos_phi", &Frame3f::cos_phi, "v"_a, D(Frame, cos_phi)) 28 | .def_static("cos_phi_2", &Frame3f::cos_phi_2, "v"_a, D(Frame, cos_phi_2)) 29 | .def_static("sincos_phi", &Frame3f::sincos_phi, "v"_a, D(Frame, sincos_phi)) 30 | .def_static("sincos_phi_2", &Frame3f::sincos_phi_2, "v"_a, D(Frame, sincos_phi_2)) 31 | 32 | .def_field(Frame3f, s) 33 | .def_field(Frame3f, t) 34 | .def_field(Frame3f, n) 35 | .def_repr(Frame3f); 36 | 37 | MI_PY_DRJIT_STRUCT(f, Frame3f, s, t, n) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/core/python/fresolver.cpp: -------------------------------------------------------------------------------- 1 | #include // Needs to be first, to get `ref` caster 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | MI_PY_EXPORT(FileResolver) { 8 | MI_PY_CLASS(FileResolver, Object) 9 | .def(nb::init<>(), D(FileResolver, FileResolver)) 10 | .def(nb::init(), D(FileResolver, FileResolver, 2)) 11 | .def("__len__", &FileResolver::size, D(FileResolver, size)) 12 | .def("__iter__", [](const FileResolver &fr) { return nb::make_iterator( 13 | nb::type(), "fr_iterator", fr.begin(), fr.end()); }, 14 | nb::keep_alive<0, 1>()) 15 | .def("__delitem__", [](FileResolver &fr, size_t i) { 16 | if (i >= fr.size()) 17 | throw nb::index_error(); 18 | fr.erase(fr.begin() + i); 19 | }) 20 | .def("__getitem__", [](const FileResolver &fr, size_t i) -> fs::path { 21 | if (i >= fr.size()) 22 | throw nb::index_error(); 23 | return fr[i]; 24 | }) 25 | .def("__setitem__", [](FileResolver &fr, size_t i, const fs::path &value) { 26 | if (i >= fr.size()) 27 | throw nb::index_error(); 28 | fr[i] = value; 29 | }) 30 | .def_method(FileResolver, resolve) 31 | .def_method(FileResolver, clear) 32 | .def_method(FileResolver, prepend) 33 | .def_method(FileResolver, append); 34 | } 35 | -------------------------------------------------------------------------------- /src/core/python/misc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | MI_PY_EXPORT(misc) { 6 | auto misc = m.def_submodule("misc", "Miscellaneous utility routines"); 7 | 8 | misc.def("core_count", &util::core_count, D(util, core_count)) 9 | .def("time_string", &util::time_string, D(util, time_string), "time"_a, "precise"_a = false) 10 | .def("mem_string", &util::mem_string, D(util, mem_string), "size"_a, "precise"_a = false) 11 | .def("trap_debugger", &util::trap_debugger, D(util, trap_debugger)); 12 | } 13 | -------------------------------------------------------------------------------- /src/core/python/mmap.cpp: -------------------------------------------------------------------------------- 1 | #include // Needs to be first, to get `ref` caster 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | MI_PY_EXPORT(MemoryMappedFile) { 9 | MI_PY_CLASS(MemoryMappedFile, Object) 10 | .def(nb::init(), 11 | D(MemoryMappedFile, MemoryMappedFile), "filename"_a, "size"_a) 12 | .def(nb::init(), 13 | D(MemoryMappedFile, MemoryMappedFile, 2), "filename"_a, "write"_a = false) 14 | .def("__init__", [](MemoryMappedFile* t, 15 | const mitsuba::filesystem::path &p, 16 | nb::ndarray array) { 17 | size_t size = array.size() * array.itemsize(); 18 | auto m = new (t) MemoryMappedFile(p, size); 19 | memcpy(m->data(), array.data(), size); 20 | }, "filename"_a, "array"_a) 21 | .def("size", &MemoryMappedFile::size, D(MemoryMappedFile, size)) 22 | .def("data", nb::overload_cast<>(&MemoryMappedFile::data), D(MemoryMappedFile, data)) 23 | .def("resize", &MemoryMappedFile::resize, D(MemoryMappedFile, resize)) 24 | .def("filename", &MemoryMappedFile::filename, D(MemoryMappedFile, filename)) 25 | .def("can_write", &MemoryMappedFile::can_write, D(MemoryMappedFile, can_write)) 26 | .def_static("create_temporary", &MemoryMappedFile::create_temporary, D(MemoryMappedFile, create_temporary)) 27 | .def("__array__", [](MemoryMappedFile &m) { 28 | return nb::ndarray((uint8_t*) m.data(), { m.size() }, nb::handle()); 29 | }, nb::rv_policy::reference_internal); 30 | } 31 | -------------------------------------------------------------------------------- /src/core/python/qmc_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | MI_PY_EXPORT(qmc) { 7 | MI_PY_IMPORT_TYPES() 8 | 9 | MI_PY_CHECK_ALIAS(RadicalInverse, "RadicalInverse") { 10 | nb::class_(m, "RadicalInverse", D(RadicalInverse)) 11 | .def(nb::init(), "max_base"_a = 8161, "scramble"_a = -1) 12 | .def("base", &RadicalInverse::base, D(RadicalInverse, base)) 13 | .def("bases", &RadicalInverse::bases, D(RadicalInverse, bases)) 14 | .def("scramble", &RadicalInverse::scramble, D(RadicalInverse, scramble)) 15 | .def("eval", &RadicalInverse::eval, "base_index"_a, "index"_a, 16 | D(RadicalInverse, eval)) 17 | // .def("eval_scrambled", &RadicalInverse::eval_scrambled, "base_index"_a, 18 | // "index"_a, D(RadicalInverse, eval_scrambled)) 19 | .def("permutation", 20 | [](nb::object self, uint32_t index) { 21 | const RadicalInverse &s = nb::cast(self); 22 | return nb::ndarray(s.permutation(index), {s.base(index)}, self); 23 | }, 24 | D(RadicalInverse, permutation)) 25 | .def("inverse_permutation", &RadicalInverse::inverse_permutation, 26 | D(RadicalInverse, inverse_permutation)); 27 | 28 | m.def("radical_inverse_2", radical_inverse_2, 29 | "index"_a, "scramble"_a, D(radical_inverse_2)); 30 | 31 | m.def("sobol_2", sobol_2, 32 | "index"_a, "scramble"_a, D(sobol_2)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/python/quad_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | MI_PY_EXPORT(quad) { 8 | MI_PY_IMPORT_TYPES() 9 | using FloatX = DynamicBuffer; 10 | m.def("gauss_legendre", &quad::gauss_legendre, "n"_a, D(quad, gauss_legendre)); 11 | m.def("gauss_lobatto", &quad::gauss_lobatto, "n"_a, D(quad, gauss_lobatto)); 12 | m.def("composite_simpson", &quad::composite_simpson, "n"_a, 13 | D(quad, composite_simpson)); 14 | m.def("composite_simpson_38", &quad::composite_simpson_38, "n"_a, 15 | D(quad, composite_simpson_38)); 16 | m.def("chebyshev", &quad::chebyshev, "n"_a, 17 | D(quad, chebyshev)); 18 | } 19 | -------------------------------------------------------------------------------- /src/core/python/random_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | MI_PY_EXPORT(sample_tea) { 6 | MI_PY_IMPORT_TYPES() 7 | 8 | if constexpr (dr::is_jit_v) { 9 | m.def("sample_tea_32", sample_tea_32, 10 | "v0"_a, "v1"_a, "rounds"_a = 4, D(sample_tea_32)); 11 | 12 | m.def("sample_tea_64", sample_tea_64, 13 | "v0"_a, "v1"_a, "rounds"_a = 4, D(sample_tea_64)); 14 | 15 | m.def("sample_tea_float32", 16 | sample_tea_float32, 17 | "v0"_a, "v1"_a, "rounds"_a = 4, D(sample_tea_float32)); 18 | 19 | m.def("sample_tea_float64", 20 | sample_tea_float64, 21 | "v0"_a, "v1"_a, "rounds"_a = 4, D(sample_tea_float64)); 22 | } 23 | 24 | m.def("sample_tea_32", sample_tea_32, 25 | "v0"_a, "v1"_a, "rounds"_a = 4, D(sample_tea_32)); 26 | 27 | m.def("sample_tea_64", sample_tea_64, 28 | "v0"_a, "v1"_a, "rounds"_a = 4, D(sample_tea_64)); 29 | 30 | m.def("sample_tea_float32", 31 | sample_tea_float32, 32 | "v0"_a, "v1"_a, "rounds"_a = 4, D(sample_tea_float32)); 33 | 34 | m.def("sample_tea_float64", 35 | sample_tea_float64, 36 | "v0"_a, "v1"_a, "rounds"_a = 4, D(sample_tea_float64)); 37 | 38 | m.attr("sample_tea_float") = m.attr( 39 | sizeof(dr::scalar_t) != sizeof(dr::scalar_t) ? "sample_tea_float32" : "sample_tea_float64"); 40 | 41 | m.def("permute", 42 | permute, 43 | "value"_a, "size"_a, "seed"_a, "rounds"_a = 4, D(permute)); 44 | 45 | m.def("permute_kensler", 46 | permute_kensler, 47 | "i"_a, "l"_a, "p"_a, "active"_a = true, D(permute_kensler)); 48 | } 49 | -------------------------------------------------------------------------------- /src/core/python/rfilter_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | template 6 | void bind_rfilter(nb::module_ &m, const char *name) { 7 | MI_PY_CHECK_ALIAS(ReconstructionFilter, name) { 8 | nb::class_( 9 | m, name, D(ReconstructionFilter)) 10 | .def("border_size", &ReconstructionFilter::border_size, 11 | D(ReconstructionFilter, border_size)) 12 | .def("is_box_filter", &ReconstructionFilter::is_box_filter, 13 | D(ReconstructionFilter, is_box_filter)) 14 | .def("radius", &ReconstructionFilter::radius, 15 | D(ReconstructionFilter, radius)) 16 | .def("eval", &ReconstructionFilter::eval, 17 | D(ReconstructionFilter, eval), "x"_a, "active"_a = true) 18 | .def("eval_discretized", &ReconstructionFilter::eval_discretized, 19 | D(ReconstructionFilter, eval_discretized), "x"_a, 20 | "active"_a = true); 21 | } 22 | } 23 | 24 | MI_PY_EXPORT(rfilter) { 25 | MI_PY_IMPORT_TYPES(ReconstructionFilter) 26 | using BitmapReconstructionFilter = Bitmap::ReconstructionFilter; 27 | 28 | bind_rfilter(m, "ReconstructionFilter"); 29 | bind_rfilter(m, "BitmapReconstructionFilter"); 30 | } 31 | -------------------------------------------------------------------------------- /src/core/python/timer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | MI_PY_EXPORT(Timer) { 6 | nb::class_(m, "Timer") 7 | .def(nb::init<>()) 8 | .def_method(Timer, value) 9 | .def_method(Timer, reset) 10 | .def_method(Timer, begin_stage) 11 | .def_method(Timer, end_stage); 12 | } 13 | -------------------------------------------------------------------------------- /src/core/python/vector_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | MI_PY_EXPORT(vector) { 8 | MI_PY_IMPORT_TYPES() 9 | m.def("coordinate_system", &coordinate_system>, "n"_a, 10 | D(coordinate_system)); 11 | m.def("sph_to_dir", &sph_to_dir, "theta"_a, "phi"_a, 12 | D(sph_to_dir)); 13 | m.def("dir_to_sph", &dir_to_sph, "v"_a, 14 | D(dir_to_sph)); 15 | } 16 | -------------------------------------------------------------------------------- /src/core/rfilter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | NAMESPACE_BEGIN(mitsuba) 5 | 6 | MI_VARIANT ReconstructionFilter::ReconstructionFilter(const Properties &/*props*/) { } 7 | MI_VARIANT ReconstructionFilter::~ReconstructionFilter() { } 8 | 9 | MI_VARIANT void ReconstructionFilter::init_discretization() { 10 | Assert(m_radius > 0); 11 | 12 | if constexpr (!dr::is_jit_v) { 13 | m_values.resize(MI_FILTER_RESOLUTION + 1); 14 | 15 | // Evaluate and store the filter values 16 | for (size_t i = 0; i < MI_FILTER_RESOLUTION; ++i) 17 | m_values[i] = eval((m_radius * i) / MI_FILTER_RESOLUTION); 18 | 19 | m_values[MI_FILTER_RESOLUTION] = 0; 20 | } 21 | 22 | m_scale_factor = MI_FILTER_RESOLUTION / m_radius; 23 | m_border_size = (int) dr::ceil(m_radius - .5f - 2.f * math::RayEpsilon); 24 | } 25 | 26 | MI_VARIANT bool ReconstructionFilter::is_box_filter() const { 27 | // The box filter is the only filter in Mitsuba 3 with 1/2px radius 28 | return m_radius == .5f; 29 | } 30 | 31 | 32 | std::ostream &operator<<(std::ostream &os, const FilterBoundaryCondition &value) { 33 | switch (value) { 34 | case FilterBoundaryCondition::Clamp: os << "clamp"; break; 35 | case FilterBoundaryCondition::Repeat: os << "repeat"; break; 36 | case FilterBoundaryCondition::Mirror: os << "mirror"; break; 37 | case FilterBoundaryCondition::Zero: os << "zero"; break; 38 | case FilterBoundaryCondition::One: os << "one"; break; 39 | default: os << "invalid"; break; 40 | } 41 | return os; 42 | } 43 | 44 | MI_IMPLEMENT_CLASS_VARIANT(ReconstructionFilter, Object, "rfilter") 45 | MI_INSTANTIATE_CLASS(ReconstructionFilter) 46 | NAMESPACE_END(mitsuba) 47 | -------------------------------------------------------------------------------- /src/core/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/core/tests/__init__.py -------------------------------------------------------------------------------- /src/core/tests/test_argparser.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mitsuba as mi 3 | 4 | 5 | def test01_short_args(variant_scalar_rgb): 6 | ap = mi.ArgParser() 7 | a = ap.add("-a") 8 | b = ap.add("-b") 9 | c = ap.add("-c") 10 | d = ap.add("-d") 11 | ap.parse(['mitsuba', '-aba', '-d']) 12 | 13 | assert bool(a) 14 | assert a.count() == 2 15 | assert bool(b) 16 | assert b.count() == 1 17 | assert not bool(c) 18 | assert c.count() == 0 19 | assert bool(d) 20 | assert d.count() == 1 21 | assert ap.executable_name() == "mitsuba" 22 | 23 | 24 | def test02_parameter_value(variant_scalar_rgb): 25 | ap = mi.ArgParser() 26 | a = ap.add("-a", True) 27 | ap.parse(['mitsuba', '-a', 'abc', '-axyz']) 28 | assert bool(a) 29 | assert a.count() == 2 30 | assert a.as_string() == 'abc' 31 | with pytest.raises(RuntimeError): 32 | a.as_int() 33 | assert a.next().as_string() == 'xyz' 34 | 35 | 36 | def test03_parameter_missing(variant_scalar_rgb): 37 | ap = mi.ArgParser() 38 | ap.add("-a", True) 39 | with pytest.raises(RuntimeError): 40 | ap.parse(['mitsuba', '-a']) 41 | 42 | 43 | def test04_parameter_float_and_extra_args(variant_scalar_rgb): 44 | ap = mi.ArgParser() 45 | f = ap.add("-f", True) 46 | other = ap.add("", True) 47 | ap.parse(['mitsuba', 'other', '-f0.25', 'arguments']) 48 | assert bool(f) 49 | assert f.as_float() == 0.25 50 | assert bool(other) 51 | assert other.count() == 2 52 | 53 | 54 | def test05_long_parameters_failure(variant_scalar_rgb): 55 | ap = mi.ArgParser() 56 | i = ap.add("--int", True) 57 | with pytest.raises(RuntimeError): 58 | ap.parse(['mitsuba', '--int1']) 59 | with pytest.raises(RuntimeError): 60 | ap.parse(['mitsuba', '--int']) 61 | ap.parse(['mitsuba', '--int', '34']) 62 | assert i.as_int() == 34 63 | -------------------------------------------------------------------------------- /src/core/tests/test_atomic.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | 3 | import pytest 4 | import mitsuba as mi 5 | 6 | 7 | def test01_add(variant_scalar_rgb): 8 | f = mi.AtomicFloat(5) 9 | threads = [] 10 | 11 | def increment(f): 12 | for i in range(10): 13 | f += 1 14 | 15 | for j in range(0, 10): 16 | threads.append(Thread(target=increment, args=(f,))) 17 | for t in threads: 18 | t.start() 19 | for t in threads: 20 | t.join() 21 | assert float(f) == 105 22 | -------------------------------------------------------------------------------- /src/core/tests/test_bsphere.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_basics(variant_scalar_rgb): 7 | bsphere1 = mi.BoundingSphere3f() 8 | bsphere2 = mi.BoundingSphere3f([0, 1, 2], 1) 9 | 10 | assert str(bsphere1) == "BoundingSphere3f[empty]" 11 | assert str(bsphere2) == "BoundingSphere3f[\n center = [0, 1, 2],\n radius = 1\n]" 12 | assert bsphere1.radius == 0 13 | assert dr.all(bsphere1.center == [0, 0, 0]) 14 | assert bsphere2.radius == 1 15 | assert dr.all(bsphere2.center == [0, 1, 2]) 16 | assert bsphere1 != bsphere2 17 | assert bsphere2 == bsphere2 18 | assert bsphere1.empty() 19 | assert not bsphere2.empty() 20 | bsphere1.expand([0, 1, 0]) 21 | assert not bsphere1.empty() 22 | assert bsphere1.contains([0, 0, 1]) 23 | assert not bsphere1.contains([0, 0, 1], strict=True) 24 | 25 | 26 | def test02_ray_intersect(variant_scalar_rgb): 27 | bsphere = mi.BoundingSphere3f([0, 0, 0], 1) 28 | 29 | hit, mint, maxt = bsphere.ray_intersect(mi.Ray3f([-5, 0, 0], [1, 0, 0])) 30 | assert hit and dr.allclose(mint, 4.0) and dr.allclose(maxt, 6.0) 31 | 32 | hit, mint, maxt = bsphere.ray_intersect(mi.Ray3f([-1, -1, -1], dr.normalize(mi.Vector3f(1)))) 33 | assert hit and dr.allclose(mint, dr.sqrt(3) - 1) and dr.allclose(maxt, dr.sqrt(3) + 1) 34 | 35 | hit, mint, maxt = bsphere.ray_intersect(mi.Ray3f([-2, 0, 0], [0, 1, 0])) 36 | assert not hit 37 | 38 | 39 | def test06_ray_intersect_vec(variant_scalar_rgb): 40 | def kernel(o, center, radius): 41 | bsphere = mi.BoundingSphere3f(center, radius) 42 | hit, mint, maxt = bsphere.ray_intersect(mi.Ray3f(o, dr.normalize(-o))) 43 | 44 | mint = dr.select(hit, mint, -1.0) 45 | maxt = dr.select(hit, maxt, -1.0) 46 | 47 | return mint, maxt 48 | 49 | from mitsuba.test.util import check_vectorization 50 | check_vectorization(kernel, arg_dims = [3, 3, 1]) 51 | -------------------------------------------------------------------------------- /src/core/tests/test_logger.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_custom(variant_scalar_rgb): 7 | # Install a custom formatter and appender and process a log message 8 | messages = [] 9 | 10 | logger = mi.Thread.thread().logger() 11 | formatter = logger.formatter() 12 | appenders = [] 13 | while logger.appender_count() > 0: 14 | app = logger.appender(0) 15 | appenders.append(app) 16 | logger.remove_appender(app) 17 | 18 | try: 19 | class MyFormatter(mi.Formatter): 20 | def format(self, level, theClass, thread, filename, line, msg): 21 | return "%i: class=%s, thread=%s, text=%s, filename=%s, ' \ 22 | 'line=%i" % (level, str(theClass), thread.name(), msg, 23 | filename, line) 24 | 25 | class MyAppender(mi.Appender): 26 | def append(self, level, text): 27 | messages.append(text) 28 | 29 | logger.set_formatter(MyFormatter()) 30 | logger.add_appender(MyAppender()) 31 | 32 | mi.Log(mi.LogLevel.Warn, "This is a test message") 33 | assert len(messages) == 1 34 | assert messages[0].startswith( 35 | '300: class=None, thread=main, text=test01_custom(): This is a' 36 | ' test message, filename=') 37 | finally: 38 | logger.clear_appenders() 39 | for app in appenders: 40 | logger.add_appender(app) 41 | logger.set_formatter(formatter) 42 | -------------------------------------------------------------------------------- /src/core/tests/test_quad.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mitsuba as mi 3 | import drjit as dr 4 | from drjit.scalar import ArrayXf as Float 5 | 6 | 7 | def test01_gauss_lobatto(variant_scalar_rgb): 8 | gauss_lobatto = mi.quad.gauss_lobatto 9 | 10 | assert dr.allclose(gauss_lobatto(2), [[-1, 1], [1.0, 1.0]]) 11 | assert dr.allclose(gauss_lobatto(3), [[-1, 0, 1], [1.0/3.0, 4.0/3.0, 1.0/3.0]]) 12 | assert dr.allclose(gauss_lobatto(4), [[-1, -dr.sqrt(1.0/5.0), dr.sqrt(1.0/5.0), 1], [1.0/6.0, 5.0/6.0, 5.0/6.0, 1.0/6.0]]) 13 | assert dr.allclose(gauss_lobatto(5), [[-1, -dr.sqrt(3.0/7.0), 0, dr.sqrt(3.0/7.0), 1], [1.0/10.0, 49.0/90.0, 32.0/45.0, 49.0/90.0, 1.0/10.0]]) 14 | 15 | 16 | def test02_gauss_legendre(variant_scalar_rgb): 17 | gauss_legendre = mi.quad.gauss_legendre 18 | 19 | assert dr.allclose(gauss_legendre(1), [[0], [2]]) 20 | assert dr.allclose(gauss_legendre(2), [[-dr.sqrt(1.0/3.0), dr.sqrt(1.0/3.0)], [1, 1]]) 21 | assert dr.allclose(gauss_legendre(3), [[-dr.sqrt(3.0/5.0), 0, dr.sqrt(3.0/5.0)], [5.0/9.0, 8.0/9.0, 5.0/9.0]]) 22 | assert dr.allclose(gauss_legendre(4), [[-0.861136, -0.339981, 0.339981, 0.861136, ], [0.347855, 0.652145, 0.652145, 0.347855]]) 23 | 24 | 25 | def test03_composite_simpson(variant_scalar_rgb): 26 | composite_simpson = mi.quad.composite_simpson 27 | 28 | assert dr.allclose(composite_simpson(3), [dr.linspace(Float, -1, 1, 3), [1.0/3.0, 4.0/3.0, 1.0/3.0]]) 29 | assert dr.allclose(composite_simpson(5), [dr.linspace(Float, -1, 1, 5), [.5/3.0, 2/3.0, 1/3.0, 2/3.0, .5/3.0]]) 30 | 31 | 32 | def test04_composite_simpson_38(variant_scalar_rgb): 33 | composite_simpson_38 = mi.quad.composite_simpson_38 34 | 35 | assert dr.allclose(composite_simpson_38(4), [dr.linspace(Float, -1, 1, 4), [0.25, 0.75, 0.75, 0.25]]) 36 | assert dr.allclose(composite_simpson_38(7), [dr.linspace(Float, -1, 1, 7), [0.125, 0.375, 0.375, 0.25 , 0.375, 0.375, 0.125]], atol=1e-6) 37 | -------------------------------------------------------------------------------- /src/core/tests/test_spectrum.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | import numpy as np 6 | import os 7 | 8 | def test01_roundtrip_file_text(variant_scalar_rgb, np_rng, tmpdir): 9 | wav = list(np_rng.random(48)) 10 | val = list(np_rng.random(48)) 11 | 12 | tmp_file = os.path.join(str(tmpdir), "test_spectrum.spd") 13 | mi.spectrum_to_file(tmp_file, wav, val) 14 | 15 | wav2, val2 = mi.spectrum_from_file(tmp_file) 16 | assert dr.allclose(wav, wav2) 17 | assert dr.allclose(val, val2) 18 | 19 | def test02_format_not_valid(variant_scalar_rgb, tmpdir): 20 | wav = [2.0, 4.0] 21 | val = [1.0, 2.0] 22 | tmp_file = os.path.join(str(tmpdir), "test_spectrum.spf") 23 | with pytest.raises(RuntimeError): 24 | mi.spectrum_to_file(tmp_file, wav, val) 25 | with pytest.raises(RuntimeError): 26 | _, _ = mi.spectrum_from_file(tmp_file) 27 | 28 | -------------------------------------------------------------------------------- /src/core/tests/test_thread.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mitsuba as mi 3 | from threading import Thread 4 | 5 | 6 | def test01_use_scoped_thread_environment(variant_scalar_rgb): 7 | def use_scoped_set_thread_env(env): 8 | with mi.ScopedSetThreadEnvironment(environment): 9 | mi.Log(mi.LogLevel.Info, 'Log from a thread environment.') 10 | 11 | environment = mi.ThreadEnvironment() 12 | thread = Thread(target=use_scoped_set_thread_env, args=(environment, )) 13 | thread.start() 14 | thread.join() 15 | 16 | 17 | def test02_log_from_new_thread(variant_scalar_rgb, tmp_path): 18 | 19 | # We use a StreamAppender to capture the output. 20 | log_path = tmp_path / 'log.txt' 21 | appender = mi.StreamAppender(str(log_path)) 22 | 23 | log_str = 'Log from a thread environment.' 24 | 25 | def print_to_log(): 26 | logger = mi.Thread.thread().logger() 27 | assert logger is not None 28 | logger.add_appender(appender) 29 | mi.set_log_level(mi.LogLevel.Info) 30 | mi.Log(mi.LogLevel.Info, log_str) 31 | 32 | thread = Thread(target=print_to_log) 33 | thread.start() 34 | thread.join() 35 | 36 | log = appender.read_log() 37 | assert 'print_to_log' in log 38 | assert log_str in log 39 | 40 | 41 | def test03_access_file_resolver_from_new_thread(variant_scalar_rgb): 42 | def access_fresolver(): 43 | fs = mi.Thread.thread().file_resolver() 44 | assert fs is not None 45 | fs.resolve('./some_path') 46 | n_paths = len(fs) 47 | fs.prepend('./some_folder') 48 | fs.prepend('./some_folder2') 49 | assert len(fs) == n_paths + 2 50 | 51 | fs = mi.Thread.thread().file_resolver() 52 | n_paths = len(fs) 53 | 54 | thread = Thread(target=access_fresolver) 55 | thread.start() 56 | thread.join() 57 | 58 | # The original file resolver remains unchanged. 59 | assert len(fs) == n_paths 60 | -------------------------------------------------------------------------------- /src/core/tests/test_util.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | 4 | 5 | def test01_time_string(variant_scalar_rgb): 6 | from mitsuba.misc import time_string 7 | 8 | assert time_string(1, precise=True) == '1ms' 9 | assert time_string(2010, precise=True) == '2.01s' 10 | assert time_string(2 * 1000 * 60, precise=True) == '2m' 11 | assert time_string(2 * 1000 * 60 * 60, precise=True) == '2h' 12 | assert time_string(2 * 1000 * 60 * 60 * 24, precise=True) == '2d' 13 | assert time_string(2 * 1000 * 60 * 60 * 24 * 7, precise=True) == '2w' 14 | assert time_string(2 * 1000 * 60 * 60 * 24 * 7 * 52.1429, 15 | precise=True) == '2y' 16 | 17 | 18 | def test01_mem_string(variant_scalar_rgb): 19 | from mitsuba.misc import mem_string 20 | 21 | assert mem_string(2, precise=True) == '2 B' 22 | assert mem_string(2 * 1024, precise=True) == '2 KiB' 23 | assert mem_string(2 * 1024 ** 2, precise=True) == '2 MiB' 24 | assert mem_string(2 * 1024 ** 3, precise=True) == '2 GiB' 25 | if sys.maxsize > 4*1024**3: 26 | assert mem_string(2 * 1024 ** 4, precise=True) == '2 TiB' 27 | assert mem_string(2 * 1024 ** 5, precise=True) == '2 PiB' 28 | assert mem_string(2 * 1024 ** 6, precise=True) == '2 EiB' 29 | -------------------------------------------------------------------------------- /src/core/tests/test_variants.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mitsuba as mi 3 | 4 | 5 | def test01_variants_callbacks(variants_all_backends_once): 6 | available = mi.variants() 7 | if len(available) <= 1: 8 | pytest.mark.skip("Test requires more than 1 enabled variant") 9 | 10 | history = [] 11 | change_count = 0 12 | def track_changes(old, new): 13 | history.append((old, new)) 14 | def count_changes(old, new): 15 | nonlocal change_count 16 | change_count += 1 17 | 18 | mi.detail.add_variant_callback(track_changes) 19 | mi.detail.add_variant_callback(count_changes) 20 | # Adding the same callback multiple times does nothing. 21 | # It won't be called multiple times. 22 | mi.detail.add_variant_callback(track_changes) 23 | mi.detail.add_variant_callback(track_changes) 24 | 25 | try: 26 | previous = mi.variant() 27 | base_i = available.index(previous) 28 | 29 | expected = [] 30 | for i in range(1, len(available) + 1): 31 | next_i = (base_i + i) % len(available) 32 | next_variant = available[next_i] 33 | 34 | assert next_variant != mi.variant() 35 | mi.set_variant(next_variant) 36 | expected.append((previous, next_variant)) 37 | previous = next_variant 38 | 39 | assert len(expected) > 1 40 | assert len(history) == len(expected) 41 | assert change_count == len(expected) 42 | for e, h in zip(expected, history): 43 | assert h == e 44 | 45 | finally: 46 | # The callback shouldn't stay on even if the test fails. 47 | mi.detail.remove_variant_callback(track_changes) 48 | 49 | # Callback shouldn't be called anymore 50 | len_e = len(expected) 51 | next_variant = available[(available.index(mi.variant()) + 1) % len(available)] 52 | with mi.util.scoped_set_variant(next_variant): 53 | pass 54 | assert len(expected) == len_e 55 | -------------------------------------------------------------------------------- /src/core/tests/test_vector.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | from drjit.scalar import ArrayXf as Float 4 | import mitsuba as mi 5 | 6 | 7 | def test01_coordinate_system(variant_scalar_rgb): 8 | def branchless_onb(n): 9 | """ 10 | Building an Orthonormal Basis, Revisited 11 | Tom Duff, James Burgess, Per Christensen, Christophe Hery, 12 | Andrew Kensler, Max Liani, and Ryusuke Villemin 13 | """ 14 | sign = dr.copysign(1.0, n[2]) 15 | a = -1.0 / (sign + n[2]) 16 | b = n[0] * n[1] * a 17 | return ( 18 | [1.0 + sign * n[0] * n[0] * a, sign * b, -sign * n[0]], 19 | [b, sign + n[1] * n[1] * a, -n[1]] 20 | ) 21 | 22 | a = [0.70710678, -0. , -0.70710678] 23 | b = [-0., 1., 0.] 24 | assert dr.allclose( 25 | branchless_onb([dr.sqrt(0.5), 0, dr.sqrt(0.5)]), (a, b), atol=1e-6) 26 | assert dr.allclose( 27 | mi.coordinate_system([dr.sqrt(0.5), 0, dr.sqrt(0.5)]), (a, b), atol=1e-6) 28 | 29 | for u in dr.linspace(Float, 0, 1, 10): 30 | for v in dr.linspace(Float, 0, 1, 10): 31 | n = mi.warp.square_to_uniform_sphere([u, v]) 32 | s1, t1 = branchless_onb(n) 33 | s2, t2 = mi.coordinate_system(n) 34 | assert dr.allclose(s1, s2, atol=1e-6) 35 | assert dr.allclose(t1, t2, atol=1e-6) 36 | 37 | 38 | def test02_coordinate_system_vec(variant_scalar_rgb): 39 | 40 | def kernel(u : float, v : float): 41 | n = mi.warp.square_to_uniform_sphere([u, v]) 42 | return mi.coordinate_system(n) 43 | 44 | from mitsuba.test.util import check_vectorization 45 | check_vectorization(kernel) -------------------------------------------------------------------------------- /src/emitters/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "emitters") 2 | 3 | add_plugin(area area.cpp) 4 | add_plugin(point point.cpp) 5 | add_plugin(constant constant.cpp) 6 | add_plugin(envmap envmap.cpp) 7 | add_plugin(directional directional.cpp) 8 | add_plugin(directionalarea directionalarea.cpp) 9 | add_plugin(spot spot.cpp) 10 | add_plugin(projector projector.cpp) 11 | add_plugin(sunsky sunsky.cpp) 12 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 13 | -------------------------------------------------------------------------------- /src/emitters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/emitters/__init__.py -------------------------------------------------------------------------------- /src/emitters/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/emitters/tests/__init__.py -------------------------------------------------------------------------------- /src/emitters/tests/test_projector.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | from mitsuba.scalar_rgb.test.util import fresolver_append_path 6 | 7 | @fresolver_append_path 8 | def test01_sampling_weights(variants_vec_backends_once_rgb): 9 | rng = mi.PCG32(size=102400) 10 | sample = mi.Point2f( 11 | rng.next_float32(), 12 | rng.next_float32()) 13 | sample_2 = mi.Point2f( 14 | rng.next_float32(), 15 | rng.next_float32()) 16 | 17 | emitter = mi.load_dict({ 18 | "type" : "envmap", 19 | 'filename': 'resources/data/common/textures/museum.exr', 20 | }) 21 | 22 | # Test the sample_direction() interface 23 | si = dr.zeros(mi.SurfaceInteraction3f) 24 | ds, w = emitter.sample_direction(si, sample) 25 | si.wi = -ds.d 26 | w2 = emitter.eval(si) / emitter.pdf_direction(si, ds) 27 | w3 = emitter.eval_direction(si, ds) / ds.pdf 28 | 29 | assert dr.allclose(w, w2, rtol=1e-2) 30 | assert dr.allclose(w, w3, rtol=1e-2) 31 | 32 | # Test the sample_ray() interface 33 | ray, w = emitter.sample_ray(0, 0, sample, sample_2) 34 | si.wi = ray.d 35 | ds.d = -ray.d 36 | w4 = emitter.eval(si) / emitter.pdf_direction(si, ds) * dr.pi 37 | assert dr.allclose(w4, w, rtol=1e-2) 38 | -------------------------------------------------------------------------------- /src/films/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "films") 2 | 3 | add_plugin(hdrfilm hdrfilm.cpp) 4 | add_plugin(specfilm specfilm.cpp) 5 | 6 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 7 | -------------------------------------------------------------------------------- /src/integrators/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "integrators") 2 | 3 | add_plugin(aov aov.cpp) 4 | add_plugin(depth depth.cpp) 5 | add_plugin(direct direct.cpp) 6 | add_plugin(moment moment.cpp) 7 | add_plugin(path path.cpp) 8 | add_plugin(ptracer ptracer.cpp) 9 | add_plugin(stokes stokes.cpp) 10 | add_plugin(volpath volpath.cpp) 11 | add_plugin(volpathmis volpathmis.cpp) 12 | 13 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 14 | -------------------------------------------------------------------------------- /src/integrators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/integrators/__init__.py -------------------------------------------------------------------------------- /src/integrators/depth.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | NAMESPACE_BEGIN(mitsuba) 5 | 6 | /**! 7 | 8 | .. _integrator-depth: 9 | 10 | Depth integrator (:monosp:`depth`) 11 | ---------------------------------- 12 | 13 | Example of one an extremely simple type of integrator that is also 14 | helpful for debugging: returns the distance from the camera to the closest 15 | intersected object, or 0 if no intersection was found. 16 | 17 | .. tabs:: 18 | .. code-tab:: xml 19 | :name: depth-integrator 20 | 21 | 22 | 23 | .. code-tab:: python 24 | 25 | 'type': 'depth' 26 | 27 | */ 28 | 29 | template 30 | class DepthIntegrator final : public SamplingIntegrator { 31 | public: 32 | MI_IMPORT_BASE(SamplingIntegrator) 33 | MI_IMPORT_TYPES(Scene, Sampler, Medium) 34 | 35 | DepthIntegrator(const Properties &props) : Base(props) { } 36 | 37 | std::pair sample(const Scene *scene, 38 | Sampler * /* sampler */, 39 | const RayDifferential3f &ray, 40 | const Medium * /* medium */, 41 | Float * /* aovs */, 42 | Mask active) const override { 43 | MI_MASKED_FUNCTION(ProfilerPhase::SamplingIntegratorSample, active); 44 | 45 | PreliminaryIntersection3f pi = scene->ray_intersect_preliminary( 46 | ray, /* coherent = */ true, active); 47 | 48 | return { 49 | dr::select(pi.is_valid(), pi.t, 0.f), 50 | pi.is_valid() 51 | }; 52 | } 53 | 54 | MI_DECLARE_CLASS() 55 | }; 56 | 57 | MI_IMPLEMENT_CLASS_VARIANT(DepthIntegrator, SamplingIntegrator) 58 | MI_EXPORT_PLUGIN(DepthIntegrator, "Depth integrator"); 59 | NAMESPACE_END(mitsuba) 60 | -------------------------------------------------------------------------------- /src/integrators/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/integrators/tests/__init__.py -------------------------------------------------------------------------------- /src/integrators/tests/test_integrators.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | def test01_trampoline_id(variants_vec_backends_once_rgb): 6 | class DummyIntegrator(mi.SamplingIntegrator): 7 | def __init__(self, props): 8 | mi.SamplingIntegrator.__init__(self, props) 9 | self.depth = props['depth'] 10 | 11 | def traverse(self, callback): 12 | callback.put_parameter('depth', self.depth, mi.ParamFlags.NonDifferentiable) 13 | 14 | mi.register_integrator('dummy_integrator', DummyIntegrator) 15 | 16 | scene_description = mi.cornell_box() 17 | del scene_description['integrator'] 18 | scene_description['my_integrator'] = { 19 | 'type': 'dummy_integrator', 20 | 'depth': 3.1 21 | } 22 | scene = mi.load_dict(scene_description) 23 | 24 | params = mi.traverse(scene) 25 | assert 'my_integrator.depth' in params 26 | 27 | 28 | def test02_path_directly_visible(variants_all_rgb): 29 | scene_description = mi.cornell_box() 30 | # Look only at light 31 | scene_description['sensor']['film']['crop_offset_x'] = 124 32 | scene_description['sensor']['film']['crop_offset_y'] = 36 33 | scene_description['sensor']['film']['crop_width'] = 1 34 | scene_description['sensor']['film']['crop_height'] = 1 35 | scene = mi.load_dict(scene_description) 36 | 37 | integrator = mi.load_dict({ 38 | 'type': 'path', 39 | 'max_depth': 1, 40 | }) 41 | img = mi.render(scene, integrator=integrator) 42 | assert dr.allclose(img.array, [18.387, 13.9873, 6.75357]) 43 | -------------------------------------------------------------------------------- /src/media/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "media") 2 | 3 | add_plugin(homogeneous homogeneous.cpp) 4 | add_plugin(heterogeneous heterogeneous.cpp) 5 | 6 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 7 | -------------------------------------------------------------------------------- /src/media/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/media/tests/__init__.py -------------------------------------------------------------------------------- /src/media/tests/test_homogeneous.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mitsuba as mi 3 | 4 | 5 | def test01_phase_function_accessors(variants_vec_rgb): 6 | medium = mi.load_dict ({ 7 | 'type': 'homogeneous', 8 | 'albedo': { 9 | 'type': 'rgb', 10 | 'value': [0.99, 0.9, 0.96] 11 | } 12 | }) 13 | medium_ptr = mi.MediumPtr(medium) 14 | 15 | assert type(medium.phase_function()) == mi.PhaseFunction 16 | assert type(medium_ptr.phase_function()) == mi.PhaseFunctionPtr 17 | -------------------------------------------------------------------------------- /src/mitsuba/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories( 2 | ${ASMJIT_INCLUDE_DIRS} 3 | ) 4 | 5 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 6 | 7 | add_executable(mitsuba-bin mitsuba.cpp) 8 | 9 | target_link_libraries(mitsuba-bin PRIVATE mitsuba) 10 | 11 | if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") 12 | target_link_libraries(mitsuba-bin PRIVATE asmjit) 13 | endif() 14 | 15 | if (UNIX AND NOT APPLE) 16 | target_link_libraries(mitsuba-bin PRIVATE dl) 17 | endif() 18 | 19 | set_target_properties(mitsuba-bin PROPERTIES OUTPUT_NAME mitsuba) 20 | -------------------------------------------------------------------------------- /src/phase/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "phasefunctions") 2 | 3 | add_plugin(hg hg.cpp) 4 | add_plugin(isotropic isotropic.cpp) 5 | add_plugin(blendphase blendphase.cpp) 6 | add_plugin(rayleigh rayleigh.cpp) 7 | add_plugin(sggx sggx.cpp) 8 | add_plugin(tabphase tabphase.cpp) 9 | 10 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 11 | -------------------------------------------------------------------------------- /src/phase/tests/test_hg.py: -------------------------------------------------------------------------------- 1 | import mitsuba as mi 2 | import pytest 3 | 4 | 5 | def test01_create(variant_scalar_rgb): 6 | p = mi.load_dict({"type": "hg", "g": 0.4}) 7 | assert p is not None 8 | 9 | 10 | @pytest.mark.parametrize('g', ['0.6', '-0.6']) 11 | def test02_chi2(variants_vec_backends_once_rgb, g): 12 | sample_func, pdf_func = mi.chi2.PhaseFunctionAdapter("hg", f'') 13 | 14 | chi2 = mi.chi2.ChiSquareTest( 15 | domain=mi.chi2.SphericalDomain(), 16 | sample_func=sample_func, 17 | pdf_func=pdf_func, 18 | sample_dim=3, 19 | ) 20 | 21 | assert chi2.run() 22 | -------------------------------------------------------------------------------- /src/phase/tests/test_isotropic.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_create(variant_scalar_rgb): 7 | p = mi.load_dict({"type": "isotropic"}) 8 | assert p is not None 9 | 10 | 11 | def test02_eval(variants_vec_backends_once_rgb): 12 | p = mi.load_dict({"type": "isotropic"}) 13 | ctx = mi.PhaseFunctionContext() 14 | mei = mi.MediumInteraction3f() 15 | 16 | theta = dr.linspace(mi.Float, 0, dr.pi / 2, 4) 17 | ph = dr.linspace(mi.Float, 0, dr.pi, 4) 18 | wo = [dr.sin(theta), 0, dr.cos(theta)] 19 | v_eval = p.eval_pdf(ctx, mei, wo)[0] 20 | 21 | assert dr.allclose(v_eval, 1.0 / (4 * dr.pi)) 22 | 23 | 24 | def test03_chi2(variants_vec_backends_once_rgb): 25 | sample_func, pdf_func = mi.chi2.PhaseFunctionAdapter("isotropic", "") 26 | 27 | chi2 = mi.chi2.ChiSquareTest( 28 | domain=mi.chi2.SphericalDomain(), 29 | sample_func=sample_func, 30 | pdf_func=pdf_func, 31 | sample_dim=3, 32 | ) 33 | 34 | assert chi2.run() 35 | -------------------------------------------------------------------------------- /src/phase/tests/test_rayleigh.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | def test_create(variant_scalar_rgb): 6 | p = mi.load_dict({"type": "rayleigh"}) 7 | assert p is not None 8 | 9 | 10 | def test_chi2(variants_vec_backends_once_rgb): 11 | sample_func, pdf_func = mi.chi2.PhaseFunctionAdapter("rayleigh", "") 12 | 13 | chi2 = mi.chi2.ChiSquareTest( 14 | domain=mi.chi2.SphericalDomain(), 15 | sample_func=sample_func, 16 | pdf_func=pdf_func, 17 | sample_dim=3, 18 | ) 19 | 20 | assert chi2.run() 21 | -------------------------------------------------------------------------------- /src/phase/tests/test_sggx.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | import os 5 | 6 | 7 | def test01_create(variant_scalar_rgb, tmpdir): 8 | tmp_file = os.path.join(str(tmpdir), "sggx.vol") 9 | grid = mi.TensorXf([1.0, 1.0, 1.0, 0.0, 0.0, 0.0], (1, 1, 1, 6)) 10 | mi.VolumeGrid(grid).write(tmp_file) 11 | 12 | p = mi.load_string(f""" 13 | 14 | 15 | 16 | """) 17 | 18 | assert p is not None 19 | assert p.flags() == mi.PhaseFunctionFlags.Anisotropic | mi.PhaseFunctionFlags.Microflake 20 | 21 | 22 | @pytest.mark.slow 23 | def test02_chi2_simple(variants_vec_backends_once_rgb, tmpdir): 24 | tmp_file = os.path.join(str(tmpdir), "sggx.vol") 25 | grid = mi.TensorXf([0.15, 1.0, 0.8, 0.0, 0.0, 0.0], (1, 1, 1, 6)) 26 | mi.VolumeGrid(grid).write(tmp_file) 27 | 28 | sample_func, pdf_func = mi.chi2.PhaseFunctionAdapter("sggx", 29 | f""" 30 | 31 | 32 | """) 33 | 34 | chi2 = mi.chi2.ChiSquareTest( 35 | domain=mi.chi2.SphericalDomain(), 36 | sample_func=sample_func, 37 | pdf_func=pdf_func, 38 | sample_dim=3 39 | ) 40 | 41 | assert chi2.run() 42 | 43 | 44 | @pytest.mark.slow 45 | def test03_chi2_skewed(variants_vec_backends_once_rgb, tmpdir): 46 | tmp_file = os.path.join(str(tmpdir), "sggx.vol") 47 | grid = mi.TensorXf([1.0, 0.35, 0.32, 0.52, 0.44, 0.2], (1, 1, 1, 6)) 48 | mi.VolumeGrid(grid).write(tmp_file) 49 | 50 | sample_func, pdf_func = mi.chi2.PhaseFunctionAdapter("sggx", 51 | f""" 52 | 53 | 54 | """) 55 | 56 | chi2 = mi.chi2.ChiSquareTest( 57 | domain=mi.chi2.SphericalDomain(), 58 | sample_func=sample_func, 59 | pdf_func=pdf_func, 60 | sample_dim=3 61 | ) 62 | 63 | assert chi2.run() 64 | -------------------------------------------------------------------------------- /src/phase/tests/test_trampoline.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_create_and_eval(variants_vec_rgb): 7 | class MyIsotropicPhaseFunction(mi.PhaseFunction): 8 | def __init__(self, props): 9 | mi.PhaseFunction.__init__(self, props) 10 | self.m_flags = mi.PhaseFunctionFlags.Isotropic 11 | 12 | def sample(self, ctx, mei, sample1, sample2, active=True): 13 | wo = mi.warp.square_to_uniform_sphere(sample2) 14 | pdf = mi.warp.square_to_uniform_sphere_pdf(wo) 15 | return (wo, 1.0, pdf) 16 | 17 | def eval_pdf(self, ctx, mei, wo, active=True): 18 | pdf = mi.warp.square_to_uniform_sphere_pdf(wo) 19 | return pdf, pdf 20 | 21 | def to_string(self): 22 | return "MyIsotropicPhaseFunction[]" 23 | 24 | mi.register_phasefunction("myisotropic", lambda props: MyIsotropicPhaseFunction(props)) 25 | 26 | p = mi.load_dict({'type': 'myisotropic'}) 27 | assert p is not None 28 | 29 | assert mi.has_flag(p.m_flags, mi.PhaseFunctionFlags.Isotropic) 30 | assert not mi.has_flag(p.m_flags, mi.PhaseFunctionFlags.Anisotropic) 31 | assert not mi.has_flag(p.m_flags, mi.PhaseFunctionFlags.Microflake) 32 | 33 | ctx = mi.PhaseFunctionContext() 34 | mei = mi.MediumInteraction3f() 35 | theta = dr.linspace(mi.Float, 0, dr.pi / 2, 4) 36 | ph = dr.linspace(mi.Float, 0, dr.pi, 4) 37 | 38 | wo = [dr.sin(theta), 0, dr.cos(theta)] 39 | v_eval, v_pdf = p.eval_pdf(ctx, mei, wo) 40 | 41 | assert dr.allclose(v_pdf, 1.0 / (4 * dr.pi)) 42 | -------------------------------------------------------------------------------- /src/python/__init__.py: -------------------------------------------------------------------------------- 1 | """ Mitsuba Python extension library """ 2 | 3 | import sys as _sys 4 | import os as _os 5 | import drjit as _dr 6 | import logging 7 | 8 | if _sys.version_info < (3, 8): 9 | raise ImportError("Mitsuba requires Python 3.8 or greater.") 10 | 11 | mi_dir = _os.path.dirname(_os.path.realpath(__file__)) 12 | drjit_expected_loc = _os.path.realpath(_os.path.join(mi_dir, "..", "drjit")) 13 | drjit_loc = _os.path.realpath(_dr.__path__[0]) 14 | if _os.name != 'nt' and drjit_expected_loc != drjit_loc: 15 | logging.warning("The `mitsuba` package relies on `drjit` and needs it " 16 | "to be installed at a specific location. Currently, " 17 | "`drjit` is located at \"%s\" when it is expected to be " 18 | "at \"%s\". This can happen when both packages are not " 19 | "installed in the same Python environment. You will very " 20 | "likely experience linking issues if you do not fix this." 21 | % (drjit_loc, drjit_expected_loc)) 22 | del mi_dir, drjit_expected_loc, drjit_loc 23 | 24 | from .config import DRJIT_VERSION_REQUIREMENT 25 | if _dr.__version__ != DRJIT_VERSION_REQUIREMENT: 26 | raise ImportError("You are using an incompatible version of `drjit`. " 27 | "Only version \"%s\" is guaranteed to be compatible with " 28 | "your current Mitsuba installation. Please update your " 29 | "Python packages for `drjit` and/or `mitsuba`." 30 | % (DRJIT_VERSION_REQUIREMENT)) 31 | del DRJIT_VERSION_REQUIREMENT 32 | 33 | with _dr.detail.scoped_rtld_deepbind(): 34 | # Replaces 'mitsuba' in sys.modules with itself (mitsuba_alias) 35 | from . import mitsuba_alias 36 | 37 | _ = mitsuba_alias # Removes unused variable warnings 38 | -------------------------------------------------------------------------------- /src/python/mitsuba_stubs/__init__.py: -------------------------------------------------------------------------------- 1 | import mitsuba as mi 2 | import sys 3 | import os 4 | 5 | def stub_variant() -> str: 6 | tokens = ['rgb', 'spectral', 'polarized', 'ad', 'llvm', 'cuda'] 7 | d = {} 8 | 9 | variants = mi.variants() 10 | for t in tokens: 11 | d[t] = any(t in v for v in variants) 12 | 13 | variant = '' 14 | if d['llvm']: 15 | variant += 'llvm_' 16 | elif d['cuda']: 17 | variant += 'cuda_' 18 | else: 19 | variant += 'scalar_' 20 | 21 | if d['ad']: 22 | variant += 'ad_' 23 | 24 | if d['spectral']: 25 | variant += 'spectral' 26 | elif d['rgb']: 27 | variant += 'rgb' 28 | else: 29 | variant += 'mono' 30 | 31 | if d['polarized']: 32 | variant += '_polarized' 33 | 34 | return variant 35 | 36 | v = stub_variant() 37 | 38 | if v not in mi.variants(): 39 | raise ImportError(f'Based on chosen set of Mitsuba variants, variant {v} ' 40 | 'is required for stub generation. Please modify your ' 41 | 'mitsuba.conf file to include this variant and recompile ' 42 | 'Mitsuba.') 43 | 44 | # Mitsuba variant has static initialization that requires JIT to initialize 45 | # For generating stubs we don't actually care if JIT init is successful 46 | # e.g. if LLVM/CUDA wasn't installed 47 | # We set an envvar to signal to the module that it's only being loaded for stub 48 | # generation and can therefore skip any static initializations. 49 | os.environ["MI_STUB_GENERATION"] = "True" 50 | try: 51 | mi.set_variant(v) 52 | finally: 53 | del os.environ["MI_STUB_GENERATION"] 54 | 55 | sys.modules[__name__] = sys.modules['mitsuba'] 56 | sys.modules[__name__ +'.math'] = mi.math 57 | sys.modules[__name__ +'.spline'] = mi.spline 58 | sys.modules[__name__ +'.warp'] = mi.warp 59 | sys.modules[__name__ +'.quad'] = mi.quad 60 | sys.modules[__name__ +'.mueller'] = mi.mueller 61 | sys.modules[__name__ +'.misc'] = mi.misc 62 | sys.modules[__name__ +'.python'] = mi.python 63 | sys.modules[__name__ +'.detail'] = mi.detail 64 | sys.modules[__name__ +'.filesystem'] = mi.filesystem 65 | 66 | -------------------------------------------------------------------------------- /src/python/python/__init__.py: -------------------------------------------------------------------------------- 1 | from .util import traverse, SceneParameters, render, cornell_box, variant_context, scoped_set_variant 2 | from . import chi2 3 | from . import xml 4 | from . import ad 5 | from . import math_py 6 | from . import testing 7 | -------------------------------------------------------------------------------- /src/python/python/ad/__init__.py: -------------------------------------------------------------------------------- 1 | from .largesteps import LargeSteps 2 | from .integrators import * 3 | from .optimizers import * 4 | from .guiding import * 5 | from .projective import * 6 | -------------------------------------------------------------------------------- /src/python/python/ad/integrators/__init__.py: -------------------------------------------------------------------------------- 1 | # Import/re-import all files in this folder to register AD integrators 2 | import importlib 3 | import mitsuba as mi 4 | 5 | if mi.variant() is not None and not mi.variant().startswith('scalar'): 6 | from . import common 7 | importlib.reload(common) 8 | 9 | from . import prb_basic 10 | importlib.reload(prb_basic) 11 | 12 | from . import prb 13 | importlib.reload(prb) 14 | 15 | from . import prbvolpath 16 | importlib.reload(prbvolpath) 17 | 18 | from . import direct_projective 19 | importlib.reload(direct_projective) 20 | 21 | from . import prb_projective 22 | importlib.reload(prb_projective) 23 | 24 | from . import volprim_rf_basic 25 | importlib.reload(volprim_rf_basic) 26 | 27 | del importlib, mi 28 | -------------------------------------------------------------------------------- /src/python/python/ad/optimizers.py: -------------------------------------------------------------------------------- 1 | from typing import Mapping 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | # The built-in Mitsuba optimizers have been replaced with an improved and 6 | # general implementation in Dr.Jit. More information is available at the 7 | # following URL: 8 | # https://drjit.readthedocs.io/en/latest/reference.html#module-drjit.opt 9 | 10 | from drjit.opt import Optimizer, Adam, SGD, RMSProp, GradScaler 11 | 12 | # Patch the Dr.Jit optimizers to ignore non-differentiable scene parameters 13 | def _filter_func(self, params: Mapping[str, dr.ArrayBase]) -> Mapping[str, dr.ArrayBase]: 14 | if type(params) is mi.SceneParameters: 15 | filtered = { } 16 | for k, v in params.items(): 17 | if params.flags(k) & mi.ParamFlags.NonDifferentiable.value: 18 | continue 19 | filtered[k] = v 20 | return filtered 21 | else: 22 | return params 23 | 24 | Optimizer._filter = _filter_func 25 | -------------------------------------------------------------------------------- /src/python/python/cli.py: -------------------------------------------------------------------------------- 1 | def _main(): 2 | import os, sys, subprocess 3 | import mitsuba # This import will check runtime requirements (ex: DrJit version) 4 | 5 | for p in sys.path: 6 | mi_package = os.path.join(p, "mitsuba") 7 | if os.path.isdir(mi_package): 8 | os_ext = "" 9 | env = os.environ.copy() 10 | if os.name == "nt": 11 | os_ext = ".exe" 12 | env["PATH"] = os.path.join(p, "drjit") + os.pathsep + env["PATH"] 13 | 14 | mi_executable = os.path.join(mi_package, "mitsuba" + os_ext) 15 | args = [mi_executable] + sys.argv[1:] 16 | process = subprocess.run(args, env=env) 17 | 18 | exit(process.returncode) 19 | 20 | print("Could not find mitsuba executable!", file=sys.stderr) 21 | exit(-1) 22 | 23 | 24 | if __name__ == "__main__": 25 | _main() 26 | -------------------------------------------------------------------------------- /src/python/python/math_py.py: -------------------------------------------------------------------------------- 1 | import drjit as dr 2 | 3 | def rlgamma(a, x): 4 | 'Regularized lower incomplete gamma function based on CEPHES' 5 | 6 | eps = 1e-15 7 | big = 4.503599627370496e15 8 | biginv = 2.22044604925031308085e-16 9 | 10 | if a < 0 or x < 0: 11 | raise "out of range" 12 | 13 | if x == 0: 14 | return 0 15 | 16 | ax = (a * dr.log(x)) - x - dr.lgamma(a) 17 | 18 | if ax < -709.78271289338399: 19 | return 1.0 if a < x else 0.0 20 | 21 | if x <= 1 or x <= a: 22 | r2 = a 23 | c2 = 1 24 | ans2 = 1 25 | 26 | while True: 27 | r2 = r2 + 1 28 | c2 = c2 * x / r2 29 | ans2 += c2 30 | 31 | if not (c2 / ans2 > eps): 32 | break 33 | 34 | return dr.exp(ax) * ans2 / a 35 | 36 | c = 0 37 | y = 1 - a 38 | z = x + y + 1 39 | p3 = 1 40 | q3 = x 41 | p2 = x + 1 42 | q2 = z * x 43 | ans = p2 / q2 44 | 45 | while True: 46 | c += 1 47 | y += 1 48 | z += 2 49 | yc = y * c 50 | p = (p2 * z) - (p3 * yc) 51 | q = (q2 * z) - (q3 * yc) 52 | 53 | if q != 0: 54 | nextans = p / q 55 | error = dr.abs((ans - nextans) / nextans) 56 | ans = nextans 57 | else: 58 | error = 1 59 | 60 | p3 = p2 61 | p2 = p 62 | q3 = q2 63 | q2 = q 64 | 65 | # normalize fraction when the numerator becomes large 66 | if dr.abs(p) > big: 67 | p3 *= biginv 68 | p2 *= biginv 69 | q3 *= biginv 70 | q2 *= biginv 71 | 72 | if not (error > eps): 73 | break 74 | 75 | return 1 - dr.exp(ax) * ans 76 | -------------------------------------------------------------------------------- /src/python/python/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/python/python/test/__init__.py -------------------------------------------------------------------------------- /src/python/python/tonemap.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | import sys, os 3 | import argparse 4 | from concurrent.futures import ThreadPoolExecutor 5 | import mitsuba as mi 6 | mi.set_variant('scalar_rgb') 7 | 8 | mi.set_log_level(mi.LogLevel.Info) 9 | te = mi.ThreadEnvironment() 10 | 11 | def tonemap(fname, scale): 12 | with mi.ScopedSetThreadEnvironment(te): 13 | try: 14 | img_in = mi.Bitmap(fname) 15 | if scale != 1: 16 | img_in = mi.Bitmap(mi.TensorXf(img_in) * scale) 17 | img_out = img_in.convert(mi.Bitmap.PixelFormat.RGB, mi.Struct.Type.UInt8, True) 18 | fname_out = fname.replace('.exr', '.png') 19 | img_out.write(fname_out) 20 | mi.Log(mi.LogLevel.Info, 'Wrote "%s".' % fname_out) 21 | except Exception as e: 22 | sys.stderr.write('Could not tonemap image "%s": %s!\n' % 23 | (fname, str(e))) 24 | 25 | class MyParser(argparse.ArgumentParser): 26 | def error(self, message): 27 | sys.stderr.write('error: %s\n' % message) 28 | self.print_help() 29 | sys.exit(2) 30 | 31 | parser = MyParser(description= 32 | 'This program loads a sequence of EXR files and writes tonemapped PNGs using ' 33 | 'a sRGB response curve. Blue-noise dithering is applied to avoid banding ' 34 | 'artifacts in this process.') 35 | parser.add_argument('--scale', type=float, default='1', help='Optional scale factor that should be applied before tonemapping') 36 | parser.add_argument('file', type=str, nargs='+', help='One more more EXR files to be processed') 37 | args = parser.parse_args() 38 | 39 | with ThreadPoolExecutor() as exc: 40 | for f in args.file: 41 | if os.path.exists(f): 42 | exc.submit(tonemap, f, args.scale) 43 | else: 44 | sys.stderr.write('File "%s" not found!\n' % f) 45 | -------------------------------------------------------------------------------- /src/python_tests/__init__.py: -------------------------------------------------------------------------------- 1 | # We need to separate these tests from the `src/python/python` folder or else 2 | # pytest will re-import the `__init__.py` files, which will lead to errors. 3 | -------------------------------------------------------------------------------- /src/python_tests/test_largesteps.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | from mitsuba.scalar_rgb.test.util import fresolver_append_path 6 | 7 | @fresolver_append_path 8 | def test01_init(variants_all_ad_rgb): 9 | pytest.importorskip("cholespy") 10 | 11 | mesh = mi.load_dict({ 12 | "type" : "ply", 13 | "filename" : "resources/data/tests/ply/triangle.ply", 14 | }) 15 | params = mi.traverse(mesh) 16 | 17 | 18 | lambda_ = 25 19 | ls = mi.ad.LargeSteps(params['vertex_positions'], params['faces'], lambda_) 20 | 21 | 22 | @fresolver_append_path 23 | def test02_roundtrip(variants_all_ad_rgb): 24 | pytest.importorskip("cholespy") 25 | 26 | mesh = mi.load_dict({ 27 | "type" : "ply", 28 | "filename" : "resources/data/tests/ply/triangle.ply", 29 | }) 30 | params = mi.traverse(mesh) 31 | 32 | lambda_ = 25 33 | ls = mi.ad.LargeSteps(params['vertex_positions'], params['faces'], lambda_) 34 | 35 | initial = params['vertex_positions'] 36 | roundtrip = ls.from_differential(ls.to_differential(params['vertex_positions'])) 37 | assert dr.allclose(initial, roundtrip, atol=1e-6) 38 | 39 | 40 | def test03_non_unique_vertices(variants_all_ad_rgb): 41 | pytest.importorskip("cholespy") 42 | 43 | mesh = mi.Mesh("MyMesh", 5, 2) 44 | params = mi.traverse(mesh) 45 | params['vertex_positions'] = [ 46 | 0.0, 0.0, 0.0, 47 | 1.0, 0.0, 0.0, 48 | 0.0, 1.0, 0.0, 49 | 1.0, 0.0, 0.0, 50 | 1.0, 1.0, 0.0, 51 | 0.0, 1.0, 0.0, 52 | ] 53 | params['faces'] = [0, 1, 2, 3, 4, 5] 54 | params.update() 55 | 56 | lambda_ = 25 57 | ls = mi.ad.LargeSteps(params['vertex_positions'], params['faces'], lambda_) 58 | assert ls.n_verts == 4 59 | -------------------------------------------------------------------------------- /src/render/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/render/__init__.py -------------------------------------------------------------------------------- /src/render/emitter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | NAMESPACE_BEGIN(mitsuba) 7 | 8 | MI_VARIANT Emitter::Emitter(const Properties &props) 9 | : Base(props) { 10 | m_sampling_weight = props.get("sampling_weight", 1.0f); 11 | 12 | MI_REGISTRY_PUT("Emitter", this); 13 | } 14 | 15 | MI_VARIANT Emitter::~Emitter() { 16 | if constexpr (dr::is_jit_v) 17 | jit_registry_remove(this); 18 | } 19 | 20 | MI_VARIANT 21 | void Emitter::traverse(TraversalCallback *callback) { 22 | callback->put_parameter("sampling_weight", m_sampling_weight, +ParamFlags::NonDifferentiable); 23 | } 24 | 25 | MI_VARIANT 26 | void Emitter::parameters_changed(const std::vector &keys) { 27 | set_dirty(true); 28 | Base::parameters_changed(keys); 29 | } 30 | 31 | MI_IMPLEMENT_CLASS_VARIANT(Emitter, Endpoint, "emitter") 32 | MI_INSTANTIATE_CLASS(Emitter) 33 | NAMESPACE_END(mitsuba) 34 | -------------------------------------------------------------------------------- /src/render/microfacet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | NAMESPACE_BEGIN(mitsuba) 4 | 5 | MI_INSTANTIATE_CLASS(MicrofacetDistribution) 6 | 7 | NAMESPACE_END(mitsuba) 8 | -------------------------------------------------------------------------------- /src/render/optix/optix_rt.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | // Include all shapes CUDA headers to generate their PTX programs 7 | #include 8 | -------------------------------------------------------------------------------- /src/render/optix_api.cpp: -------------------------------------------------------------------------------- 1 | #if defined(MI_ENABLE_CUDA) 2 | 3 | #include 4 | 5 | #include 6 | #define OPTIX_API_IMPL 7 | #include 8 | #include 9 | 10 | NAMESPACE_BEGIN(mitsuba) 11 | 12 | void optix_initialize() { 13 | if (optixAccelBuild) 14 | return; 15 | 16 | jit_optix_context(); // Ensure OptiX is initialized 17 | 18 | #define L(name) name = (decltype(name)) jit_optix_lookup(#name); 19 | 20 | L(optixAccelComputeMemoryUsage); 21 | L(optixAccelBuild); 22 | L(optixAccelCompact); 23 | L(optixBuiltinISModuleGet); 24 | L(optixDenoiserCreate); 25 | L(optixDenoiserDestroy); 26 | L(optixDenoiserComputeMemoryResources); 27 | L(optixDenoiserSetup); 28 | L(optixDenoiserInvoke); 29 | L(optixDenoiserComputeIntensity); 30 | L(optixModuleCreateWithTasks); 31 | L(optixModuleGetCompilationState); 32 | L(optixTaskExecute); 33 | L(optixProgramGroupCreate); 34 | L(optixSbtRecordPackHeader); 35 | 36 | #undef L 37 | } 38 | 39 | scoped_optix_context::scoped_optix_context() { 40 | jit_cuda_push_context(jit_cuda_context()); 41 | } 42 | 43 | scoped_optix_context::~scoped_optix_context() { 44 | jit_cuda_pop_context(); 45 | } 46 | 47 | NAMESPACE_END(mitsuba) 48 | 49 | #endif // defined(MI_ENABLE_CUDA) 50 | -------------------------------------------------------------------------------- /src/render/phase.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | NAMESPACE_BEGIN(mitsuba) 6 | 7 | MI_VARIANT 8 | PhaseFunction::PhaseFunction(const Properties &props) 9 | : m_flags(+PhaseFunctionFlags::Empty), m_id(props.id()) { 10 | MI_REGISTRY_PUT("PhaseFunction", this); 11 | } 12 | 13 | MI_VARIANT PhaseFunction::~PhaseFunction() { 14 | if constexpr (dr::is_jit_v) 15 | jit_registry_remove(this); 16 | } 17 | 18 | MI_IMPLEMENT_CLASS_VARIANT(PhaseFunction, Object, "phase") 19 | MI_INSTANTIATE_CLASS(PhaseFunction) 20 | NAMESPACE_END(mitsuba) 21 | -------------------------------------------------------------------------------- /src/render/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(RENDER_PY_V_SRC 2 | ${CMAKE_CURRENT_SOURCE_DIR}/bsdf_v.cpp 3 | ${CMAKE_CURRENT_SOURCE_DIR}/emitter_v.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/endpoint_v.cpp 5 | ${CMAKE_CURRENT_SOURCE_DIR}/film_v.cpp 6 | ${CMAKE_CURRENT_SOURCE_DIR}/fresnel_v.cpp 7 | ${CMAKE_CURRENT_SOURCE_DIR}/imageblock_v.cpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/interaction_v.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/integrator_v.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/medium_v.cpp 11 | ${CMAKE_CURRENT_SOURCE_DIR}/mueller_v.cpp 12 | ${CMAKE_CURRENT_SOURCE_DIR}/microfacet_v.cpp 13 | ${CMAKE_CURRENT_SOURCE_DIR}/microflake_v.cpp 14 | ${CMAKE_CURRENT_SOURCE_DIR}/optixdenoiser_v.cpp 15 | ${CMAKE_CURRENT_SOURCE_DIR}/phase_v.cpp 16 | ${CMAKE_CURRENT_SOURCE_DIR}/records_v.cpp 17 | ${CMAKE_CURRENT_SOURCE_DIR}/sampler_v.cpp 18 | ${CMAKE_CURRENT_SOURCE_DIR}/scene_v.cpp 19 | ${CMAKE_CURRENT_SOURCE_DIR}/sensor_v.cpp 20 | ${CMAKE_CURRENT_SOURCE_DIR}/shape_v.cpp 21 | ${CMAKE_CURRENT_SOURCE_DIR}/srgb_v.cpp 22 | ${CMAKE_CURRENT_SOURCE_DIR}/texture_v.cpp 23 | ${CMAKE_CURRENT_SOURCE_DIR}/volume_v.cpp 24 | ${CMAKE_CURRENT_SOURCE_DIR}/volumegrid_v.cpp 25 | ${CMAKE_CURRENT_SOURCE_DIR}/signal.h 26 | PARENT_SCOPE 27 | ) 28 | 29 | set(RENDER_PY_SRC 30 | ${CMAKE_CURRENT_SOURCE_DIR}/emitter.cpp 31 | ${CMAKE_CURRENT_SOURCE_DIR}/bsdf.cpp 32 | ${CMAKE_CURRENT_SOURCE_DIR}/shape.cpp 33 | ${CMAKE_CURRENT_SOURCE_DIR}/microfacet.cpp 34 | ${CMAKE_CURRENT_SOURCE_DIR}/interaction.cpp 35 | ${CMAKE_CURRENT_SOURCE_DIR}/phase.cpp 36 | ${CMAKE_CURRENT_SOURCE_DIR}/sensor.cpp 37 | ${CMAKE_CURRENT_SOURCE_DIR}/spiral.cpp 38 | ${CMAKE_CURRENT_SOURCE_DIR}/film.cpp 39 | PARENT_SCOPE 40 | ) 41 | -------------------------------------------------------------------------------- /src/render/python/emitter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | MI_PY_EXPORT(EmitterExtras) { 6 | auto e = nb::enum_(m, "EmitterFlags", nb::is_arithmetic(), D(EmitterFlags)) 7 | .def_value(EmitterFlags, Empty) 8 | .def_value(EmitterFlags, DeltaPosition) 9 | .def_value(EmitterFlags, DeltaDirection) 10 | .def_value(EmitterFlags, Infinite) 11 | .def_value(EmitterFlags, Surface) 12 | .def_value(EmitterFlags, SpatiallyVarying) 13 | .def_value(EmitterFlags, Delta); 14 | } 15 | -------------------------------------------------------------------------------- /src/render/python/endpoint_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | MI_PY_EXPORT(Endpoint) { 13 | MI_PY_IMPORT_TYPES() 14 | MI_PY_CLASS(Endpoint, Object) 15 | .def_method(Endpoint, sample_ray, "time"_a, "sample1"_a, "sample2"_a, "sample3"_a, "active"_a = true) 16 | .def_method(Endpoint, sample_direction, "it"_a, "sample"_a, "active"_a = true) 17 | .def_method(Endpoint, pdf_direction, "it"_a, "ds"_a, "active"_a = true) 18 | .def_method(Endpoint, eval_direction, "it"_a, "ds"_a, "active"_a = true) 19 | .def_method(Endpoint, sample_position, "ref"_a, "ds"_a, "active"_a = true) 20 | .def_method(Endpoint, pdf_position, "ps"_a, "active"_a = true) 21 | .def_method(Endpoint, eval, "si"_a, "active"_a = true) 22 | .def_method(Endpoint, sample_wavelengths, "si"_a, "sample"_a, "active"_a = true) 23 | .def_method(Endpoint, world_transform) 24 | .def_method(Endpoint, needs_sample_2) 25 | .def_method(Endpoint, needs_sample_3) 26 | .def("get_shape", nb::overload_cast<>(&Endpoint::shape, nb::const_), D(Endpoint, shape)) 27 | .def("get_medium", nb::overload_cast<>(&Endpoint::medium, nb::const_), D(Endpoint, medium)) 28 | .def_method(Endpoint, set_shape, "shape"_a) 29 | .def_method(Endpoint, set_medium, "medium"_a) 30 | .def_method(Endpoint, set_scene, "scene"_a) 31 | .def_method(Endpoint, bbox); 32 | } 33 | -------------------------------------------------------------------------------- /src/render/python/film.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | MI_PY_EXPORT(FilmFlags) { 6 | auto e = nb::enum_(m, "FilmFlags", nb::is_arithmetic(), D(FilmFlags)) 7 | .def_value(FilmFlags, Empty) 8 | .def_value(FilmFlags, Alpha) 9 | .def_value(FilmFlags, Spectral) 10 | .def_value(FilmFlags, Special); 11 | } 12 | -------------------------------------------------------------------------------- /src/render/python/fresnel_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | MI_PY_EXPORT(fresnel) { 9 | MI_PY_IMPORT_TYPES() 10 | m.def("fresnel", 11 | &fresnel, 12 | "cos_theta_i"_a, "eta"_a, D(fresnel)) 13 | .def("fresnel_conductor", 14 | &fresnel_conductor, 15 | "cos_theta_i"_a, "eta"_a, D(fresnel_conductor)) 16 | .def("fresnel_polarized", 17 | nb::overload_cast>(&fresnel_polarized), 18 | "cos_theta_i"_a, "eta"_a, D(fresnel_polarized, 2)) 19 | .def("reflect", 20 | nb::overload_cast(&reflect), 21 | "wi"_a, D(reflect)) 22 | .def("reflect", 23 | nb::overload_cast(&reflect), 24 | "wi"_a, "m"_a, D(reflect, 2)) 25 | .def("refract", 26 | nb::overload_cast(&refract), 27 | "wi"_a, "cos_theta_t"_a, "eta_ti"_a, D(refract)) 28 | .def("refract", 29 | nb::overload_cast(&refract), 30 | "wi"_a, "m"_a, "cos_theta_t"_a, "eta_ti"_a, D(refract, 2)) 31 | .def("fresnel_diffuse_reflectance", &fresnel_diffuse_reflectance, 32 | "eta"_a, D(fresnel_diffuse_reflectance)) 33 | .def("lookup_ior", 34 | [](const Properties &props, const std::string& name, nb::object def) { 35 | if (nb::isinstance(def)) 36 | return lookup_ior(props, name, (float) nb::cast(def)); 37 | return lookup_ior(props, name, nb::cast(def)); 38 | }, 39 | "properties"_a, "name"_a, "default"_a, 40 | "Lookup IOR value in table."); 41 | } 42 | -------------------------------------------------------------------------------- /src/render/python/interaction.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | MI_PY_EXPORT(RayFlags) { 5 | auto e = nb::enum_(m, "RayFlags", nb::is_arithmetic(), D(RayFlags)) 6 | .def_value(RayFlags, Empty) 7 | .def_value(RayFlags, Minimal) 8 | .def_value(RayFlags, UV) 9 | .def_value(RayFlags, dPdUV) 10 | .def_value(RayFlags, dNGdUV) 11 | .def_value(RayFlags, dNSdUV) 12 | .def_value(RayFlags, ShadingFrame) 13 | .def_value(RayFlags, FollowShape) 14 | .def_value(RayFlags, DetachShape) 15 | .def_value(RayFlags, All) 16 | .def_value(RayFlags, AllNonDifferentiable); 17 | } 18 | -------------------------------------------------------------------------------- /src/render/python/microfacet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | MI_PY_EXPORT(MicrofacetType) { 6 | nb::enum_(m, "MicrofacetType", D(MicrofacetType), nb::is_arithmetic()) 7 | .def_value(MicrofacetType, Beckmann) 8 | .def_value(MicrofacetType, GGX); 9 | } 10 | -------------------------------------------------------------------------------- /src/render/python/phase.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | MI_PY_EXPORT(PhaseFunctionExtras) { 7 | auto e = nb::enum_(m, "PhaseFunctionFlags", nb::is_arithmetic(), D(PhaseFunctionFlags)) 8 | .def_value(PhaseFunctionFlags, Empty) 9 | .def_value(PhaseFunctionFlags, Isotropic) 10 | .def_value(PhaseFunctionFlags, Anisotropic) 11 | .def_value(PhaseFunctionFlags, Microflake); 12 | } 13 | -------------------------------------------------------------------------------- /src/render/python/sensor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | MI_PY_EXPORT(Sensor) { 6 | m.def("parse_fov", &parse_fov, "props"_a, "aspect"_a, D(parse_fov)); 7 | } 8 | -------------------------------------------------------------------------------- /src/render/python/shape.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | MI_PY_EXPORT(DiscontinuityFlags) { 6 | auto disc_flags = nb::enum_(m, "DiscontinuityFlags", nb::is_arithmetic(), D(DiscontinuityFlags)) 7 | .def_value(DiscontinuityFlags, Empty) 8 | .def_value(DiscontinuityFlags, PerimeterType) 9 | .def_value(DiscontinuityFlags, InteriorType) 10 | .def_value(DiscontinuityFlags, DirectionLune) 11 | .def_value(DiscontinuityFlags, DirectionSphere) 12 | .def_value(DiscontinuityFlags, HeuristicWalk) 13 | .def_value(DiscontinuityFlags, AllTypes); 14 | 15 | auto shape_types = nb::enum_(m, "ShapeType", nb::is_arithmetic(), D(ShapeType)) 16 | .def_value(ShapeType, Mesh) 17 | .def_value(ShapeType, Rectangle) 18 | .def_value(ShapeType, BSplineCurve) 19 | .def_value(ShapeType, Cylinder) 20 | .def_value(ShapeType, Disk) 21 | .def_value(ShapeType, LinearCurve) 22 | .def_value(ShapeType, SDFGrid) 23 | .def_value(ShapeType, Sphere) 24 | .def_value(ShapeType, Ellipsoids) 25 | .def_value(ShapeType, EllipsoidsMesh) 26 | .def_value(ShapeType, Invalid); 27 | } 28 | -------------------------------------------------------------------------------- /src/render/python/signal.h: -------------------------------------------------------------------------------- 1 | 2 | /// RAII helper to catch Ctrl-C keypresses and cancel an ongoing render job 3 | struct MI_EXPORT ScopedSignalHandler { 4 | using IntegratorT = mitsuba::Integrator; 5 | 6 | // Defined in integrator_v.cpp 7 | ScopedSignalHandler(IntegratorT *); 8 | ~ScopedSignalHandler(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/render/python/spiral.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | MI_PY_EXPORT(Spiral) { 7 | using Vector2u = typename Spiral::Vector2u; 8 | MI_PY_CLASS(Spiral, Object) 9 | .def(nb::init(), 10 | "size"_a, "offset"_a, "block_size"_a = MI_BLOCK_SIZE, "passes"_a = 1, 11 | D(Spiral, Spiral)) 12 | .def_method(Spiral, max_block_size) 13 | .def_method(Spiral, block_count) 14 | .def_method(Spiral, reset) 15 | .def_method(Spiral, next_block); 16 | } 17 | -------------------------------------------------------------------------------- /src/render/python/srgb_v.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | MI_PY_EXPORT(srgb) { 5 | MI_PY_IMPORT_TYPES() 6 | m.def("srgb_model_fetch", &srgb_model_fetch, D(srgb_model_fetch)) 7 | // .def("srgb_model_eval_rgb", &srgb_model_eval_rgb, D(srgb_model_eval_rgb)) 8 | .def("srgb_model_eval", 9 | &srgb_model_eval, dr::Array>, 10 | D(srgb_model_eval)) 11 | .def("srgb_model_mean", 12 | &srgb_model_mean>, 13 | D(srgb_model_mean)) 14 | ; 15 | } 16 | -------------------------------------------------------------------------------- /src/render/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/render/tests/__init__.py -------------------------------------------------------------------------------- /src/render/tests/test_bsdf.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | import numpy as np 5 | 6 | 7 | def test01_ctx_construct(variant_scalar_rgb): 8 | ctx = mi.BSDFContext() 9 | assert ctx.type_mask == +mi.BSDFFlags.All 10 | assert ctx.component == np.iinfo(np.uint32).max 11 | assert ctx.mode == mi.TransportMode.Radiance 12 | ctx.reverse() 13 | assert ctx.mode == mi.TransportMode.Importance 14 | 15 | # By default, all components and types are queried 16 | assert ctx.is_enabled(mi.BSDFFlags.DeltaTransmission) 17 | assert ctx.is_enabled(mi.BSDFFlags.Delta, 1) 18 | assert ctx.is_enabled(mi.BSDFFlags.DiffuseTransmission, 6) 19 | assert ctx.is_enabled(mi.BSDFFlags.Glossy, 10) 20 | 21 | 22 | def test02_bs_construct(variant_scalar_rgb): 23 | wo = [1, 0, 0] 24 | bs = mi.BSDFSample3f(wo) 25 | assert dr.allclose(bs.wo, wo) 26 | assert dr.allclose(bs.pdf, 0.0) 27 | assert dr.allclose(bs.eta, 1.0) 28 | assert bs.sampled_type == 0 29 | 30 | def test03_bsdf_attributes(variants_vec_backends_once_rgb): 31 | 32 | si = dr.zeros(mi.SurfaceInteraction3f) 33 | si.uv = [0.5, 0.5] 34 | 35 | color = [0.5, 0.6, 0.7] 36 | 37 | bsdf = mi.load_dict({ 38 | 'type': 'diffuse', 39 | 'reflectance': { 'type': 'rgb', 'value': color } 40 | }) 41 | 42 | assert dr.all(bsdf.has_attribute('reflectance')) 43 | assert not dr.all(bsdf.has_attribute('foo')) 44 | assert dr.allclose(color, bsdf.eval_attribute('reflectance', si)) 45 | assert dr.allclose(0.0, bsdf.eval_attribute('foo', si)) 46 | 47 | # Now with a custom BSDF 48 | 49 | class DummyBSDF(mi.BSDF): 50 | def __init__(self, props): 51 | mi.BSDF.__init__(self, props) 52 | self.tint = props['tint'] 53 | 54 | def traverse(self, callback): 55 | callback.put_object('tint', self.tint, mi.ParamFlags.Differentiable) 56 | 57 | mi.register_bsdf('dummy', DummyBSDF) 58 | 59 | bsdf = mi.load_dict({ 60 | 'type': 'dummy', 61 | 'tint': { 'type': 'rgb', 'value': color } 62 | }) 63 | assert dr.all(bsdf.has_attribute('tint')) 64 | assert not dr.all(bsdf.has_attribute('foo')) 65 | assert dr.allclose(color, bsdf.eval_attribute('tint', si)) 66 | assert dr.allclose(0.0, bsdf.eval_attribute('foo', si)) -------------------------------------------------------------------------------- /src/render/tests/test_dispatch.py: -------------------------------------------------------------------------------- 1 | import drjit as dr 2 | import mitsuba as mi 3 | import pytest 4 | 5 | @pytest.mark.parametrize("recorded", [True, False]) 6 | def test01_dispatch(variants_vec_rgb, recorded): 7 | dr.set_flag(dr.JitFlag.VCallRecord, recorded) 8 | 9 | bsdf1 = mi.load_dict({'type': 'diffuse'}) 10 | bsdf2 = mi.load_dict({'type': 'conductor'}) 11 | 12 | mask = mi.Bool([True, True, False, False]) 13 | 14 | bsdf_ptr = dr.select(mask, mi.BSDFPtr(bsdf1), mi.BSDFPtr(bsdf2)) 15 | 16 | def func(self, si, wo): 17 | print(f'Tracing -> {self.class_().name()}') 18 | return self.eval(mi.BSDFContext(), si, wo) 19 | 20 | si = dr.zeros(mi.SurfaceInteraction3f) 21 | si.n = [0, 0, 1] 22 | si.sh_frame = mi.Frame3f(si.n) 23 | si.wi = [0, 0, 1] 24 | wo = mi.Vector3f(0, 0, 1) 25 | ctx = mi.BSDFContext() 26 | 27 | res = dr.dispatch(bsdf_ptr, func, si, wo) 28 | dr.eval(res) 29 | 30 | assert dr.allclose(res, bsdf_ptr.eval(ctx, si, wo)) 31 | 32 | 33 | @pytest.mark.parametrize("recorded", [True, False]) 34 | def test02_dispatch_sparse_registry(variants_vec_rgb, recorded): 35 | dr.set_flag(dr.JitFlag.VCallRecord, recorded) 36 | 37 | bsdf1 = mi.load_dict({'type': 'diffuse'}) 38 | bsdf2 = mi.load_dict({'type': 'plastic'}) 39 | bsdf3 = mi.load_dict({'type': 'conductor'}) 40 | 41 | del bsdf2 42 | 43 | mask = mi.Bool([True, True, False, False]) 44 | 45 | bsdf_ptr = dr.select(mask, mi.BSDFPtr(bsdf1), mi.BSDFPtr(bsdf3)) 46 | 47 | def func(self, si, wo): 48 | return self.eval(mi.BSDFContext(), si, wo) 49 | 50 | si = dr.zeros(mi.SurfaceInteraction3f) 51 | si.n = [0, 0, 1] 52 | si.sh_frame = mi.Frame3f(si.n) 53 | si.wi = [0, 0, 1] 54 | wo = mi.Vector3f(0, 0, 1) 55 | ctx = mi.BSDFContext() 56 | 57 | res = dr.dispatch(bsdf_ptr, func, si, wo) 58 | dr.eval(res) 59 | 60 | assert dr.allclose(res, bsdf_ptr.eval(ctx, si, wo)) 61 | 62 | res = dr.dispatch(bsdf_ptr, func, si, wo) 63 | dr.eval(res) 64 | 65 | assert dr.allclose(res, bsdf_ptr.eval(ctx, si, wo)) 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/render/tests/test_emitter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_sampling_weight_getter(variants_vec_rgb): 7 | emitter0 = mi.load_dict({ 8 | 'type': 'constant', 9 | 'sampling_weight': 0.1 10 | }) 11 | emitter1 = mi.load_dict({ 12 | 'type': 'constant', 13 | 'sampling_weight': 0.3 14 | }) 15 | 16 | emitter0_ptr = mi.EmitterPtr(emitter0) 17 | emitter1_ptr = mi.EmitterPtr(emitter1) 18 | 19 | emitters = dr.select(mi.Bool([True, False, False]), emitter0_ptr, emitter1_ptr) 20 | 21 | weight = emitters.sampling_weight() 22 | assert dr.allclose(weight, [0.1, 0.3, 0.3]) 23 | 24 | params = mi.traverse(emitter1) 25 | params['sampling_weight'] = 0.7 26 | params.update() 27 | 28 | weight = emitters.sampling_weight() 29 | assert dr.allclose(weight, [0.1, 0.7, 0.7]) 30 | 31 | 32 | def test02_trampoline(variants_vec_backends_once_rgb): 33 | class DummyEmitter(mi.Emitter): 34 | def __init__(self, props): 35 | mi.Emitter.__init__(self, props) 36 | self.m_flags = mi.EmitterFlags.SpatiallyVarying 37 | 38 | def to_string(self): 39 | return f"DummyEmitter ({self.m_flags})" 40 | 41 | mi.register_emitter('dummy_emitter', DummyEmitter) 42 | emitter = mi.load_dict({ 43 | 'type': 'dummy_emitter' 44 | }) 45 | 46 | assert str(emitter) == "DummyEmitter (16)" 47 | -------------------------------------------------------------------------------- /src/render/tests/test_film.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_trampoline(variants_vec_backends_once_rgb): 7 | class DummyFilm(mi.Film): 8 | def __init__(self, props): 9 | mi.Film.__init__(self, props) 10 | self.m_flags = mi.FilmFlags.Special 11 | 12 | def to_string(self): 13 | return f"DummyFilm ({self.m_flags})" 14 | 15 | mi.register_film('dummy_film', DummyFilm) 16 | film = mi.load_dict({ 17 | 'type': 'dummy_film' 18 | }) 19 | 20 | assert str(film) == "DummyFilm (4)" 21 | -------------------------------------------------------------------------------- /src/render/tests/test_medium.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_trampoline(variants_vec_backends_once_rgb): 7 | class DummyMedium(mi.Medium): 8 | def __init__(self, props): 9 | mi.Medium.__init__(self, props) 10 | self.m_is_homogeneous = True 11 | 12 | def to_string(self): 13 | return f"DummyMedium ({self.m_is_homogeneous})" 14 | 15 | mi.register_medium('dummy_medium', DummyMedium) 16 | medium = mi.load_dict({ 17 | 'type': 'dummy_medium' 18 | }) 19 | 20 | assert str(medium) == "DummyMedium (True)" 21 | -------------------------------------------------------------------------------- /src/render/tests/test_microflake.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | import numpy as np 5 | 6 | def test01_binding_types(variants_all_rgb): 7 | x = mi.sggx_sample(mi.Frame3f(1), mi.Point2f(1), mi.SGGXPhaseFunctionParams([1,2,3,4,5,6])) 8 | assert type(x) == mi.Normal3f 9 | x = mi.sggx_sample(mi.Frame3f(1), mi.Point2f(1), [1,2,3,4,5,6]) 10 | assert type(x) == mi.Normal3f 11 | x = mi.sggx_sample(mi.Vector3f(1), mi.Point2f(1), [1,2,3,4,5,6]) 12 | assert type(x) == mi.Normal3f 13 | x = mi.sggx_pdf(mi.Vector3f(1), [1,2,3,4,5,6]) 14 | assert type(x) == mi.Float 15 | x = mi.sggx_projected_area(mi.Vector3f(1), [1,2,3,4,5,6]) 16 | assert type(x) == mi.Float -------------------------------------------------------------------------------- /src/render/tests/test_phase.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_trampoline(variants_vec_backends_once_rgb): 7 | class DummyPhaseFunction(mi.PhaseFunction): 8 | def __init__(self, props): 9 | mi.PhaseFunction.__init__(self, props) 10 | self.m_flags = mi.PhaseFunctionFlags.Anisotropic 11 | 12 | def to_string(self): 13 | return f"DummyPhaseFunction ({self.m_flags})" 14 | 15 | mi.register_phasefunction('dummy_phase', DummyPhaseFunction) 16 | phase = mi.load_dict({ 17 | 'type': 'dummy_phase' 18 | }) 19 | 20 | assert str(phase) == "DummyPhaseFunction (2)" 21 | -------------------------------------------------------------------------------- /src/render/tests/test_volumegrid.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | import numpy as np 5 | import os 6 | 7 | 8 | def test01_numpy_conversion(variants_all_scalar, np_rng): 9 | a = np_rng.random((4, 8, 16, 3)) 10 | grid = mi.VolumeGrid(a) 11 | assert dr.allclose(a, np.array(grid), atol=1e-3, rtol=1e-5) 12 | assert dr.allclose(np.max(a), grid.max()) 13 | assert dr.allclose([a.shape[2], a.shape[1], a.shape[0]], grid.size()) 14 | assert dr.allclose(a.shape[3], grid.channel_count()) 15 | 16 | # Don't ask for computation of the maximum value 17 | grid = mi.VolumeGrid(a, False) 18 | assert dr.allclose(a, np.array(grid), atol=1e-3, rtol=1e-5) 19 | assert dr.allclose(grid.max(), 0.0) 20 | assert dr.allclose([a.shape[2], a.shape[1], a.shape[0]], grid.size()) 21 | assert dr.allclose(a.shape[3], grid.channel_count()) 22 | 23 | 24 | def test02_read_write(variants_all_scalar, tmpdir, np_rng): 25 | tmp_file = os.path.join(str(tmpdir), "out.vol") 26 | grid = np_rng.random((4, 8, 16, 3)) 27 | mi.VolumeGrid(grid).write(tmp_file) 28 | loaded = mi.VolumeGrid(tmp_file) 29 | assert dr.allclose(np.array(loaded), grid) 30 | assert dr.allclose(loaded.max(), np.max(grid)) 31 | assert dr.allclose([grid.shape[2], grid.shape[1], grid.shape[0]], loaded.size()) 32 | assert dr.allclose(grid.shape[3], loaded.channel_count()) 33 | 34 | def test03_max_per_channel(variants_all_scalar, tmpdir, np_rng): 35 | tmp_file = os.path.join(str(tmpdir), "out.vol") 36 | data = np_rng.random((4, 8, 16, 3)) 37 | grid = mi.VolumeGrid(data) 38 | grid.write(tmp_file) 39 | np_max_per_channel = np.array([np.max(data[:,:,:,0]), 40 | np.max(data[:,:,:,1]), 41 | np.max(data[:,:,:,2])]) 42 | # Check direct construction compute the maximum values 43 | mi_max_per_channel = grid.max_per_channel() 44 | assert dr.allclose(np_max_per_channel, mi_max_per_channel) 45 | # Check disk construction compute the maximum values 46 | grid = mi.VolumeGrid(tmp_file) 47 | mi_max_per_channel = grid.max_per_channel() 48 | assert dr.allclose(np_max_per_channel, mi_max_per_channel) 49 | -------------------------------------------------------------------------------- /src/rfilters/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "rfilters") 2 | 3 | add_plugin(box box.cpp) 4 | add_plugin(tent tent.cpp) 5 | add_plugin(lanczos lanczos.cpp) 6 | add_plugin(mitchell mitchell.cpp) 7 | add_plugin(catmullrom catmullrom.cpp) 8 | add_plugin(gaussian gaussian.cpp) 9 | 10 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 11 | -------------------------------------------------------------------------------- /src/rfilters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/rfilters/__init__.py -------------------------------------------------------------------------------- /src/rfilters/box.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | NAMESPACE_BEGIN(mitsuba) 6 | 7 | /**! 8 | 9 | .. _rfilter-box: 10 | 11 | Box filter (:monosp:`box`) 12 | -------------------------- 13 | 14 | This is the fastest, but also about the worst possible reconstruction filter, 15 | since it is prone to severe aliasing. It is included mainly for completeness, 16 | though some rare situations may warrant its use. 17 | 18 | .. tabs:: 19 | .. code-tab:: xml 20 | :name: box-rfilter 21 | 22 | 23 | 24 | .. code-tab:: python 25 | 26 | 'type': 'box', 27 | 28 | */ 29 | 30 | template 31 | class BoxFilter final : public ReconstructionFilter { 32 | public: 33 | MI_IMPORT_BASE(ReconstructionFilter, init_discretization, m_radius) 34 | MI_IMPORT_TYPES() 35 | 36 | BoxFilter(const Properties &props) : Base(props) { 37 | m_radius = .5f; 38 | init_discretization(); 39 | } 40 | 41 | Float eval(Float x, Mask /* active */) const override { 42 | return dr::select(x >= -.5f && x < .5f, Float(1.f), Float(0.f)); 43 | } 44 | 45 | std::string to_string() const override { return "BoxFilter[]"; } 46 | 47 | MI_DECLARE_CLASS() 48 | }; 49 | 50 | MI_IMPLEMENT_CLASS_VARIANT(BoxFilter, ReconstructionFilter) 51 | MI_EXPORT_PLUGIN(BoxFilter, "Box filter"); 52 | NAMESPACE_END(mitsuba) 53 | -------------------------------------------------------------------------------- /src/rfilters/catmullrom.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | NAMESPACE_BEGIN(mitsuba) 5 | 6 | /**! 7 | 8 | .. _rfilter-catmullrom: 9 | 10 | Catmull-Rom filter (:monosp:`catmullrom`) 11 | ----------------------------------------- 12 | 13 | Special version of the Mitchell-Netravali filter with constants B and C configured 14 | to match the Catmull-Rom spline. It usually does a better job at at preserving sharp 15 | features at the cost of more ringing. 16 | 17 | .. tabs:: 18 | .. code-tab:: xml 19 | :name: catmullrom-rfilter 20 | 21 | 22 | 23 | .. code-tab:: python 24 | 25 | 'type': 'catmullrom', 26 | 27 | */ 28 | 29 | template 30 | class CatmullRomFilter final : public ReconstructionFilter { 31 | public: 32 | MI_IMPORT_BASE(ReconstructionFilter, init_discretization, m_radius) 33 | 34 | CatmullRomFilter(const Properties &props) : Base(props) { 35 | m_radius = 2.f; 36 | init_discretization(); 37 | } 38 | 39 | Float eval(Float x, dr::mask_t /* active */) const override { 40 | x = dr::abs(x); 41 | 42 | Float x2 = dr::square(x), x3 = x2*x, 43 | B = 0.f, C = .5f; 44 | 45 | Float result = (1.f / 6.f) * dr::select( 46 | x < 1, 47 | (12.f - 9.f * B - 6.f * C) * x3 + 48 | (-18.f + 12.f * B + 6.f * C) * x2 + (6.f - 2.f * B), 49 | (-B - 6.f * C) * x3 + (6.f * B + 30.f * C) * x2 + 50 | (-12.f * B - 48.f * C) * x + (8.f * B + 24.f * C) 51 | ); 52 | 53 | return dr::select(x < 2.f, result, 0.f); 54 | } 55 | 56 | std::string to_string() const override { 57 | return tfm::format("CatmullRomFilter[radius=%f]", m_radius); 58 | } 59 | 60 | MI_DECLARE_CLASS() 61 | }; 62 | 63 | MI_IMPLEMENT_CLASS_VARIANT(CatmullRomFilter, ReconstructionFilter) 64 | MI_EXPORT_PLUGIN(CatmullRomFilter, "Catmull-Rom filter"); 65 | NAMESPACE_END(mitsuba) 66 | -------------------------------------------------------------------------------- /src/rfilters/tent.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | NAMESPACE_BEGIN(mitsuba) 6 | 7 | /**! 8 | 9 | .. _rfilter-tent: 10 | 11 | Tent filter (:monosp:`tent`) 12 | ---------------------------- 13 | 14 | .. pluginparameters:: 15 | 16 | * - radius 17 | - |float| 18 | - Specifies the radius of the tent function (Default: 1.0) 19 | 20 | Simple tent (triangular) filter. This reconstruction filter never suffers 21 | from ringing and usually causes less aliasing than a naive box filter. When 22 | rendering scenes with sharp brightness discontinuities, this may be useful; 23 | otherwise, negative-lobed filters may be preferable (e.g. Mitchell-Netravali 24 | or Lanczos Sinc). 25 | 26 | .. tabs:: 27 | .. code-tab:: xml 28 | :name: tent-rfilter 29 | 30 | 31 | 32 | 33 | 34 | .. code-tab:: python 35 | 36 | 'type': 'tent', 37 | 'radius': 1.25, 38 | 39 | */ 40 | 41 | template 42 | class TentFilter final : public ReconstructionFilter { 43 | public: 44 | MI_IMPORT_BASE(ReconstructionFilter, init_discretization, m_radius) 45 | MI_IMPORT_TYPES() 46 | 47 | TentFilter(const Properties &props) : Base(props) { 48 | m_radius = props.get("radius", 1.f); 49 | m_inv_radius = 1.f / m_radius; 50 | init_discretization(); 51 | } 52 | 53 | Float eval(Float x, dr::mask_t /* active */) const override { 54 | return dr::maximum(0.f, 1.f - dr::abs(x * m_inv_radius)); 55 | } 56 | 57 | std::string to_string() const override { 58 | return tfm::format("TentFilter[radius=%f]", m_radius); 59 | } 60 | 61 | MI_DECLARE_CLASS() 62 | private: 63 | ScalarFloat m_inv_radius; 64 | }; 65 | 66 | MI_IMPLEMENT_CLASS_VARIANT(TentFilter, ReconstructionFilter) 67 | MI_EXPORT_PLUGIN(TentFilter, "Tent filter"); 68 | NAMESPACE_END(mitsuba) 69 | -------------------------------------------------------------------------------- /src/rfilters/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/rfilters/tests/__init__.py -------------------------------------------------------------------------------- /src/samplers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "samplers") 2 | 3 | add_plugin(independent independent.cpp) 4 | add_plugin(stratified stratified.cpp) 5 | add_plugin(multijitter multijitter.cpp) 6 | add_plugin(orthogonal orthogonal.cpp) 7 | add_plugin(ldsampler ldsampler.cpp) 8 | 9 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 10 | -------------------------------------------------------------------------------- /src/samplers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/samplers/__init__.py -------------------------------------------------------------------------------- /src/samplers/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/samplers/tests/__init__.py -------------------------------------------------------------------------------- /src/samplers/tests/test_independent.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | from .utils import ( 6 | check_deep_copy_sampler_scalar, 7 | check_deep_copy_sampler_wavefront, 8 | check_sampler_kernel_hash_wavefront, 9 | ) 10 | 11 | 12 | def test01_construct(variant_scalar_rgb): 13 | sampler = mi.load_dict({ 14 | "type": "independent", 15 | "sample_count": 58 16 | }) 17 | assert sampler is not None 18 | assert sampler.sample_count() == 58 19 | 20 | 21 | def test02_sample_vs_pcg32(variant_scalar_rgb): 22 | sampler = mi.load_dict({ 23 | "type": "independent", 24 | "sample_count": 8 25 | }) 26 | sampler.seed(0) 27 | 28 | # IndependentSampler uses the default-constructed PCG if no seed is provided. 29 | rng = dr.scalar.PCG32(initstate=0) 30 | for i in range(10): 31 | assert dr.all(sampler.next_1d() == rng.next_float32()) 32 | assert dr.all(sampler.next_2d() == [rng.next_float32(), rng.next_float32()], axis=None) 33 | 34 | 35 | def test03_copy_sampler_scalar(variants_any_scalar): 36 | sampler = mi.load_dict({ 37 | "type": "independent", 38 | "sample_count": 1024 39 | }) 40 | 41 | check_deep_copy_sampler_scalar(sampler) 42 | 43 | def test04_copy_sampler_wavefront(variants_vec_backends_once): 44 | sampler = mi.load_dict({ 45 | "type": "independent", 46 | "sample_count": 1024 47 | }) 48 | 49 | check_deep_copy_sampler_wavefront(sampler) 50 | 51 | def test05_jit_seed(variants_vec_rgb): 52 | sampler = mi.load_dict({ 53 | "type": "independent", 54 | }) 55 | seed = mi.UInt(0) 56 | state_before = seed.state 57 | sampler.seed(seed, 64) 58 | assert seed.state == state_before 59 | 60 | check_sampler_kernel_hash_wavefront(mi.UInt, sampler) 61 | -------------------------------------------------------------------------------- /src/samplers/tests/test_multijitter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | from .utils import ( 6 | check_uniform_scalar_sampler, 7 | check_uniform_wavefront_sampler, 8 | check_deep_copy_sampler_scalar, 9 | check_deep_copy_sampler_wavefront, 10 | check_sampler_kernel_hash_wavefront, 11 | ) 12 | 13 | 14 | def test01_multijitter_scalar(variant_scalar_rgb): 15 | sampler = mi.load_dict({ 16 | "type" : "multijitter", 17 | "sample_count" : 1024, 18 | }) 19 | 20 | check_uniform_scalar_sampler(sampler) 21 | 22 | 23 | def test02_multijitter_wavefront(variants_vec_backends_once): 24 | sampler = mi.load_dict({ 25 | "type" : "multijitter", 26 | "sample_count" : 1024, 27 | }) 28 | 29 | check_uniform_wavefront_sampler(sampler) 30 | 31 | 32 | def test03_copy_sampler_scalar(variants_any_scalar): 33 | sampler = mi.load_dict({ 34 | "type" : "multijitter", 35 | "sample_count" : 1024, 36 | }) 37 | 38 | check_deep_copy_sampler_scalar(sampler) 39 | 40 | def test04_copy_sampler_wavefront(variants_vec_backends_once): 41 | sampler = mi.load_dict({ 42 | "type" : "multijitter", 43 | "sample_count" : 1024, 44 | }) 45 | 46 | check_deep_copy_sampler_wavefront(sampler) 47 | 48 | def test05_jit_seed(variants_vec_rgb): 49 | sampler = mi.load_dict({ 50 | "type": "independent", 51 | }) 52 | seed = mi.UInt(0) 53 | state_before = seed.state 54 | sampler.seed(seed, 64) 55 | assert seed.state == state_before 56 | 57 | check_sampler_kernel_hash_wavefront(mi.UInt, sampler) 58 | -------------------------------------------------------------------------------- /src/samplers/tests/test_orthogonal.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | from .utils import ( 6 | check_uniform_scalar_sampler, 7 | check_uniform_wavefront_sampler, 8 | check_deep_copy_sampler_scalar, 9 | check_deep_copy_sampler_wavefront, 10 | check_sampler_kernel_hash_wavefront, 11 | ) 12 | 13 | 14 | def test01_orthogonal_scalar(variant_scalar_rgb): 15 | sampler = mi.load_dict({ 16 | "type" : "orthogonal", 17 | "sample_count" : 1369, 18 | }) 19 | 20 | check_uniform_scalar_sampler(sampler, res=4, atol=4.0) 21 | 22 | 23 | def test02_orthogonal_wavefront(variants_vec_backends_once): 24 | sampler = mi.load_dict({ 25 | "type" : "orthogonal", 26 | "sample_count" : 1369, 27 | }) 28 | 29 | check_uniform_wavefront_sampler(sampler, atol=4.0) 30 | 31 | 32 | def test03_copy_sampler_scalar(variants_any_scalar): 33 | sampler = mi.load_dict({ 34 | "type" : "orthogonal", 35 | "sample_count" : 1369, 36 | }) 37 | 38 | check_deep_copy_sampler_scalar(sampler) 39 | 40 | def test04_copy_sampler_wavefront(variants_vec_backends_once): 41 | sampler = mi.load_dict({ 42 | "type" : "orthogonal", 43 | "sample_count" : 1369, 44 | }) 45 | 46 | check_deep_copy_sampler_wavefront(sampler, factor=37) 47 | 48 | def test05_jit_seed(variants_vec_rgb): 49 | sampler = mi.load_dict({ 50 | "type": "independent", 51 | }) 52 | seed = mi.UInt(0) 53 | state_before = seed.state 54 | sampler.seed(seed, 64) 55 | assert seed.state == state_before 56 | 57 | check_sampler_kernel_hash_wavefront(mi.UInt, sampler) 58 | -------------------------------------------------------------------------------- /src/samplers/tests/test_stratified.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | from .utils import ( 6 | check_uniform_scalar_sampler, 7 | check_uniform_wavefront_sampler, 8 | check_deep_copy_sampler_scalar, 9 | check_deep_copy_sampler_wavefront, 10 | check_sampler_kernel_hash_wavefront, 11 | ) 12 | 13 | 14 | def test01_stratified_scalar(variant_scalar_rgb): 15 | sampler = mi.load_dict({ 16 | "type" : "stratified", 17 | "sample_count" : 1024, 18 | }) 19 | 20 | check_uniform_scalar_sampler(sampler) 21 | 22 | 23 | def test02_stratified_wavefront(variants_vec_backends_once): 24 | sampler = mi.load_dict({ 25 | "type" : "stratified", 26 | "sample_count" : 1024, 27 | }) 28 | 29 | check_uniform_wavefront_sampler(sampler) 30 | 31 | 32 | def test03_copy_sampler_scalar(variants_any_scalar): 33 | sampler = mi.load_dict({ 34 | "type" : "stratified", 35 | "sample_count" : 1024, 36 | }) 37 | 38 | check_deep_copy_sampler_scalar(sampler) 39 | 40 | 41 | def test04_copy_sampler_wavefront(variants_vec_backends_once): 42 | sampler = mi.load_dict({ 43 | "type" : "stratified", 44 | "sample_count" : 1024, 45 | }) 46 | 47 | check_deep_copy_sampler_wavefront(sampler) 48 | 49 | def test05_jit_seed(variants_vec_rgb): 50 | sampler = mi.load_dict({ 51 | "type": "independent", 52 | }) 53 | seed = mi.UInt(0) 54 | state_before = seed.state 55 | sampler.seed(seed, 64) 56 | assert seed.state == state_before 57 | 58 | check_sampler_kernel_hash_wavefront(mi.UInt, sampler) 59 | -------------------------------------------------------------------------------- /src/sensors/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "sensors") 2 | 3 | add_plugin(perspective perspective.cpp) 4 | add_plugin(orthographic orthographic.cpp) 5 | add_plugin(radiancemeter radiancemeter.cpp) 6 | add_plugin(thinlens thinlens.cpp) 7 | add_plugin(irradiancemeter irradiancemeter.cpp) 8 | add_plugin(distant distant.cpp) 9 | add_plugin(batch batch.cpp) 10 | 11 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 12 | -------------------------------------------------------------------------------- /src/sensors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/sensors/__init__.py -------------------------------------------------------------------------------- /src/sensors/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/sensors/tests/__init__.py -------------------------------------------------------------------------------- /src/shapes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "shapes") 2 | 3 | add_plugin(obj obj.cpp) 4 | add_plugin(ply ply.cpp) 5 | add_plugin(blender blender.cpp) 6 | add_plugin(serialized serialized.cpp) 7 | 8 | add_plugin(cylinder cylinder.cpp) 9 | add_plugin(disk disk.cpp) 10 | add_plugin(rectangle rectangle.cpp) 11 | add_plugin(sdfgrid sdfgrid.cpp) 12 | add_plugin(sphere sphere.cpp) 13 | add_plugin(cube cube.cpp) 14 | add_plugin(bsplinecurve bsplinecurve.cpp) 15 | add_plugin(linearcurve linearcurve.cpp) 16 | 17 | add_plugin(shapegroup shapegroup.cpp) 18 | add_plugin(instance instance.cpp) 19 | add_plugin(merge merge.cpp) 20 | 21 | add_plugin(ellipsoids ellipsoids.cpp) 22 | add_plugin(ellipsoidsmesh ellipsoidsmesh.cpp) 23 | 24 | if (MI_ENABLE_EMBREE) 25 | target_link_libraries(sphere PRIVATE embree) 26 | target_link_libraries(instance PRIVATE embree) 27 | endif() 28 | 29 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 30 | -------------------------------------------------------------------------------- /src/shapes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/shapes/__init__.py -------------------------------------------------------------------------------- /src/shapes/optix/cylinder.cuh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct OptixCylinderData { 8 | optix::BoundingBox3f bbox; 9 | optix::Transform4f to_object; 10 | float length; 11 | float radius; 12 | }; 13 | 14 | #ifdef __CUDACC__ 15 | extern "C" __global__ void __intersection__cylinder() { 16 | const OptixHitGroupData *sbt_data = (OptixHitGroupData*) optixGetSbtDataPointer(); 17 | OptixCylinderData *cylinder = (OptixCylinderData *)sbt_data->data; 18 | 19 | // Ray in intance-space 20 | Ray3f ray = get_ray(); 21 | // Ray in object-space 22 | ray = cylinder->to_object.transform_ray(ray); 23 | 24 | float ox = ray.o.x(), 25 | oy = ray.o.y(), 26 | oz = ray.o.z(), 27 | dx = ray.d.x(), 28 | dy = ray.d.y(), 29 | dz = ray.d.z(); 30 | 31 | float A = sqr(dx) + sqr(dy), 32 | B = 2.0 * (dx * ox + dy * oy), 33 | C = sqr(ox) + sqr(oy) - sqr(cylinder->radius); 34 | 35 | float near_t, far_t; 36 | bool solution_found = solve_quadratic(A, B, C, near_t, far_t); 37 | 38 | // Cylinder doesn't intersect with the segment on the ray 39 | bool out_bounds = !(near_t <= ray.maxt && far_t >= ray.mint); // NaN-aware conditionals 40 | 41 | float z_pos_near = oz + dz * near_t, 42 | z_pos_far = oz + dz * far_t; 43 | 44 | // Cylinder fully contains the segment of the ray 45 | bool in_bounds = near_t < ray.mint && far_t > ray.maxt; 46 | 47 | bool valid_intersection = 48 | solution_found && !out_bounds && !in_bounds && 49 | ((z_pos_near >= 0.f && z_pos_near <= cylinder->length && near_t > ray.mint) || 50 | (z_pos_far >= 0.f && z_pos_far <= cylinder->length && far_t < ray.maxt)); 51 | 52 | float t = (z_pos_near >= 0 && z_pos_near <= cylinder->length && near_t >= 0.f ? near_t : far_t); 53 | 54 | if (valid_intersection) 55 | optixReportIntersection(t, OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE); 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /src/shapes/optix/disk.cuh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct OptixDiskData { 7 | optix::BoundingBox3f bbox; 8 | optix::Transform4f to_object; 9 | }; 10 | 11 | #ifdef __CUDACC__ 12 | extern "C" __global__ void __intersection__disk() { 13 | const OptixHitGroupData *sbt_data = (OptixHitGroupData*) optixGetSbtDataPointer(); 14 | OptixDiskData *disk = (OptixDiskData *)sbt_data->data; 15 | 16 | // Ray in instance-space 17 | Ray3f ray = get_ray(); 18 | // Ray in object-space 19 | ray = disk->to_object.transform_ray(ray); 20 | 21 | float t = -ray.o.z() / ray.d.z(); 22 | Vector3f local = ray(t); 23 | 24 | if (local.x() * local.x() + local.y() * local.y() <= 1.f && t > ray.mint && t < ray.maxt) 25 | optixReportIntersection(t, OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE, 26 | __float_as_uint(local.x()), 27 | __float_as_uint(local.y())); 28 | } 29 | #endif 30 | -------------------------------------------------------------------------------- /src/shapes/optix/sphere.cuh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct OptixSphereData { 8 | optix::BoundingBox3f bbox; 9 | optix::Vector3f center; 10 | float radius; 11 | }; 12 | 13 | #ifdef __CUDACC__ 14 | 15 | extern "C" __global__ void __intersection__sphere() { 16 | const OptixHitGroupData *sbt_data = (OptixHitGroupData*) optixGetSbtDataPointer(); 17 | OptixSphereData *sphere = (OptixSphereData*) sbt_data->data; 18 | 19 | // Ray in instance-space 20 | Ray3f ray = get_ray(); 21 | 22 | // We define a plane which is perpendicular to the ray direction and 23 | // contains the sphere center and intersect it. We then solve the ray-sphere 24 | // intersection as if the ray origin was this new intersection point. This 25 | // additional step makes the whole intersection routine numerically more 26 | // robust. 27 | 28 | Vector3f l = ray.o - sphere->center; 29 | Vector3f d = ray.d; 30 | float plane_t = dot(-l, d) / norm(d); 31 | Vector3f plane_p = ray(plane_t); 32 | 33 | // Ray is perpendicular to the origin-center segment, 34 | // and intersection with plane is outside of the sphere 35 | if (plane_t == 0.f && norm(plane_p - sphere->center) > sphere->radius) 36 | return; 37 | 38 | Vector3f o = plane_p - sphere->center; 39 | 40 | float A = squared_norm(d); 41 | float B = 2.f * dot(o, d); 42 | float C = squared_norm(o) - sqr(sphere->radius); 43 | 44 | float near_t, far_t; 45 | bool solution_found = solve_quadratic(A, B, C, near_t, far_t); 46 | 47 | // Adjust distances for plane intersection 48 | near_t += plane_t; 49 | far_t += plane_t; 50 | 51 | // Sphere doesn't intersect with the segment on the ray 52 | bool out_bounds = !(near_t <= ray.maxt && far_t >= 0.f); // NaN-aware conditionals 53 | 54 | // Sphere fully contains the segment of the ray 55 | bool in_bounds = near_t < ray.mint && far_t > ray.maxt; 56 | 57 | float t = (near_t < 0.f ? far_t: near_t); 58 | 59 | if (solution_found && !out_bounds && !in_bounds) 60 | optixReportIntersection(t, OPTIX_HIT_KIND_TRIANGLE_FRONT_FACE); 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /src/shapes/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsuba-renderer/mitsuba3/3b8a8a002252c5b7ab557a1ed352a87cd1b995cb/src/shapes/tests/__init__.py -------------------------------------------------------------------------------- /src/spectra/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "spectra") 2 | 3 | add_plugin(blackbody blackbody.cpp) 4 | add_plugin(d65 d65.cpp) 5 | add_plugin(irregular irregular.cpp) 6 | add_plugin(regular regular.cpp) 7 | add_plugin(rawconstant rawconstant.cpp) 8 | add_plugin(srgb srgb.cpp) 9 | add_plugin(uniform uniform.cpp) 10 | 11 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 12 | -------------------------------------------------------------------------------- /src/spectra/tests/test_d65.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_chi2(variants_vec_spectral): 7 | sample_func, pdf_func = mi.chi2.SpectrumAdapter( 8 | '') 9 | 10 | chi2 = mi.chi2.ChiSquareTest( 11 | domain=mi.chi2.LineDomain(bounds=[360.0, 830.0]), 12 | sample_func=sample_func, 13 | pdf_func=pdf_func, 14 | sample_dim=1 15 | ) 16 | 17 | assert chi2.run() 18 | -------------------------------------------------------------------------------- /src/spectra/tests/test_irregular.py: -------------------------------------------------------------------------------- 1 | # Only superficial testing here, main coverage achieved 2 | # via 'src/libcore/tests/test_distr.py' 3 | 4 | import pytest 5 | import drjit as dr 6 | import mitsuba as mi 7 | 8 | 9 | @pytest.fixture() 10 | def obj(): 11 | return mi.load_dict({ 12 | "type" : "irregular", 13 | "wavelengths" : "500, 600, 650", 14 | "values" : "1, 2, .5" 15 | }) 16 | 17 | 18 | def test01_eval(variant_scalar_spectral, obj): 19 | si = mi.SurfaceInteraction3f() 20 | 21 | values = [0, 1, 1.5, 2, .5, 0] 22 | for i in range(6): 23 | si.wavelengths = 450 + 50 * i 24 | assert dr.allclose(obj.eval(si), values[i]) 25 | assert dr.allclose(obj.pdf_spectrum(si), values[i] / 212.5) 26 | 27 | with pytest.raises(RuntimeError) as excinfo: 28 | obj.eval_1(si) 29 | assert 'not implemented' in str(excinfo.value) 30 | 31 | with pytest.raises(RuntimeError) as excinfo: 32 | obj.eval_3(si) 33 | assert 'not implemented' in str(excinfo.value) 34 | 35 | 36 | def test02_sample_spectrum(variant_scalar_spectral, obj): 37 | si = mi.SurfaceInteraction3f() 38 | assert dr.allclose(obj.sample_spectrum(si, 0), [500, 212.5]) 39 | assert dr.allclose(obj.sample_spectrum(si, 1), [650, 212.5]) 40 | assert dr.allclose( 41 | obj.sample_spectrum(si, .5), 42 | [576.777, 212.5] 43 | ) 44 | -------------------------------------------------------------------------------- /src/spectra/tests/test_regular.py: -------------------------------------------------------------------------------- 1 | # Only superficial testing here, main coverage achieved 2 | # via 'src/libcore/tests/test_distr.py' 3 | 4 | import pytest 5 | import drjit as dr 6 | import mitsuba as mi 7 | 8 | 9 | @pytest.fixture() 10 | def obj(): 11 | return mi.load_dict({ 12 | "type" : "regular", 13 | "wavelength_min" : 500, 14 | "wavelength_max" : 600, 15 | "values" : "1, 2" 16 | }) 17 | 18 | 19 | def test01_eval(variant_scalar_spectral, obj): 20 | si = mi.SurfaceInteraction3f() 21 | 22 | values = [0, 1, 1.5, 2, 0] 23 | for i in range(5): 24 | si.wavelengths = 450 + 50 * i 25 | assert dr.allclose(obj.eval(si), values[i]) 26 | assert dr.allclose(obj.pdf_spectrum(si), values[i] / 150.0) 27 | 28 | with pytest.raises(RuntimeError) as excinfo: 29 | obj.eval_1(si) 30 | assert 'not implemented' in str(excinfo.value) 31 | 32 | with pytest.raises(RuntimeError) as excinfo: 33 | obj.eval_3(si) 34 | assert 'not implemented' in str(excinfo.value) 35 | 36 | 37 | def test02_sample_spectrum(variant_scalar_spectral, obj): 38 | si = mi.SurfaceInteraction3f() 39 | assert dr.allclose(obj.sample_spectrum(si, 0), [500, 150]) 40 | assert dr.allclose(obj.sample_spectrum(si, 1), [600, 150]) 41 | assert dr.allclose( 42 | obj.sample_spectrum(si, .5), 43 | [500 + 100 * (dr.sqrt(10) / 2 - 1), 150] 44 | ) 45 | -------------------------------------------------------------------------------- /src/textures/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "textures") 2 | 3 | add_plugin(bitmap bitmap.cpp) 4 | add_plugin(checkerboard checkerboard.cpp) 5 | add_plugin(mesh_attribute mesh_attribute.cpp) 6 | add_plugin(volume volume.cpp) 7 | 8 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 9 | -------------------------------------------------------------------------------- /src/volumes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MI_PLUGIN_PREFIX "volumes") 2 | 3 | add_plugin(constvolume const.cpp) 4 | add_plugin(gridvolume grid.cpp) 5 | 6 | set(MI_PLUGIN_TARGETS "${MI_PLUGIN_TARGETS}" PARENT_SCOPE) 7 | -------------------------------------------------------------------------------- /src/volumes/tests/test_constant.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import drjit as dr 3 | import mitsuba as mi 4 | 5 | 6 | def test01_constant_construct(variant_scalar_rgb): 7 | vol = mi.load_dict({ 8 | "type": "constvolume", 9 | "to_world": mi.ScalarTransform4f().scale([2, 0.2, 1]), 10 | "value": { 11 | "type": "regular", 12 | "wavelength_min": 500, 13 | "wavelength_max": 600, 14 | "values": "3.0, 3.0" 15 | } 16 | }) 17 | 18 | assert vol is not None 19 | assert vol.bbox() == mi.BoundingBox3f([0, 0, 0], [2, 0.2, 1]) 20 | 21 | 22 | def test02_constant_eval(variant_scalar_rgb): 23 | vol = mi.load_dict({ 24 | "type": "constvolume", 25 | "value": { 26 | "type" : "srgb", 27 | "color" : mi.Color3f([0.5, 1.0, 0.3]) 28 | } 29 | }) 30 | 31 | it = dr.zeros(mi.Interaction3f, 1) 32 | assert dr.allclose(vol.eval(it), mi.Color3f(0.5, 1.0, 0.3)) 33 | assert vol.bbox() == mi.BoundingBox3f([0, 0, 0], [1, 1, 1]) 34 | --------------------------------------------------------------------------------