├── .gitignore
├── README.md
├── README_ko.md
├── [talk]
└── 2025postech
│ └── Path Tracing One Day Class.pdf
├── advanced
└── hw1_problem1.(e).md
├── figsrc
├── blender_compare.ipynb
├── cbox_max2_spp1024.exr
├── cbox_max2_spp4096.exr
├── cbox_max8_rr5_spp4096.exr
├── cbox_max8_rr5_spp4096_seed0.exr
├── cbox_max8_rr5_spp4096_seed1.exr
├── cbox_max8_rr5_spp4096_seed2.exr
├── cbox_max8_rr5_spp4096_seed3.exr
├── cbox_max8_rr5_spp4096_seed4.exr
├── figgen.ipynb
├── veachmis_max2_spp1024.exr
└── veachmis_max8_rr5_spp1024.exr
├── hw1_radiometry.ipynb
├── hw2_probability.ipynb
├── hw3_directillumination.ipynb
├── hw4_pattracing.ipynb
├── lecturenote
└── 01. radiometry and light transport.md
├── scene
├── veach-mis.zip
└── veach-mis
│ ├── .DS_Store
│ ├── LICENSE.txt
│ ├── TungstenRender.exr
│ ├── TungstenRender.png
│ ├── scene.xml
│ ├── scene_delta.xml
│ └── scene_envmap.xml
├── slides
├── 01. radiometry and light transport.pdf
├── 02. probability and statistical inference.pdf
└── 03. Path tracing.pdf
├── tutorial1-1 (optional).ipynb
├── tutorial1_Sampler.ipynb
├── tutorial2_ray_intersection.ipynb
├── tutorial3_BSDF.ipynb
├── tutorial4_Emitter.ipynb
└── util.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *_sol.ipynb
2 | *_dummy.ipynb
3 | slides/*.pptx
4 | __pycache__/
5 |
6 | media/
7 | logo/
8 | manim_test.ipynb
9 |
10 | temp.py
11 |
12 | .vscode/
13 |
14 | _memo/
15 | scene/blender-material
16 | figsrc/material-appearance
17 | figsrc/radiance-constancy
18 | figsrc/blender/
19 | \[talk\]/2025postech/_old/
20 | \[talk\]/2025postech/*.pptx
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rendering Lecture using Mitsuba3
2 | Seminar materials of physically based rendering with tutorials and homework using mitsuba3-python
3 |
4 | Please do not upload your solution to online publicly.
5 |
6 |
7 |
8 | The material may not be complete and may be slow to update. All **questions, error corrections, content suggestions, and reminders to update** are always welcome using the [Discussion](https://github.com/shinyoung-yi/lecture-rendering-mitsuba/discussions) section.
9 |
10 | ## Difference from other rendering materials
11 |
12 | Physically based rendering, typically ray tracing, is an important foundation of computer graphics and vision. However, the barriers to learning it has been considered to be high due to complex C++ coding and theoretical difficulty. The materials in this repository are aimed at lowering the barrier to learning rendering, and we believe that if you follow the provided lecture materials (`./slides/*.pdf`), tutorials (`./tutotial*.ipynb`), and homework (`./hw*.ipynb`), you should be able to implement path tracing, the baseline algorithm for ray tracing, in less than **a day**.
13 |
14 |
15 |
16 | ### Features
17 |
18 | In order to minimize the barrier to learning rendering, this material have some features that are somewhat different from other materials.
19 |
20 |
21 |
22 | * Codes are written entirely in Python code using [Mitsuba 3](https://www.mitsuba-renderer.org/).
23 | * Tutorials and homework are written in Jupyter notebooks, making it easy to visualize and check intermediate variables even before a final rendered image is obtained.
24 | * Prioritizes understanding the overall ray tracing algorithm, leaving the lower-level queries used within ray tracing as optional contents.
25 | * In particular, the ray intersection query is not covered in this material. While the purpose of the query is clear, the implementation is complex and virtually impossible to be done in Python.
26 |
27 |
28 |
29 | ## Contents
30 |
31 | You can understand ray tracing algorithms very quickly by studying the following sections.
32 |
33 | 1. Radiometry: `./slides/01. radiometry and light transport.pdf`, `./hw1_radiometry.ipynb`
34 | * HW1 uses Mitsuba 3's API to use values which the scene consists of, but you don't need to study the `./tutorial*.ipynb` tutorial in this step because I have already provided the usage in the skeleton code for each problem.
35 | 2. Probability: `./slides/02. probability and statistical inference.pdf`, `./hw2_probability.ipynb`
36 | 3. Path Tracing: `./slides/TBA`, `./tutorial*.ipynb`, `./hw3_pathtracing.ipynb`, `./hw4_pathtracing.ipynb`
37 | * HW3, HW4 need to be started after reading the tutorial in `./tutorial*.ipynb`.
38 |
39 |
40 |
41 | TBA below
42 |
43 | 4. Advanced sampling: (limited form of) bidirectional path tracing, combining with analytic integration, etc.
44 | 5. Volumetric rendering
45 | 6. Differentiable rendering: detached vs. attached sampling, boundary integral vs. reparameterization, etc. It would be nice to have a compelling visualization of the complex methodologies that appear only in differentiable rendering, and easy examples to make the concepts clear in simple situations.
46 |
47 | ## Dependencies
48 |
49 | Python (tested on 3.11) modules
50 |
51 | * NumPy
52 | * Matplotlib
53 | * mitsuba 3
54 | * Dr. Jit
55 |
56 |
57 | You can install all the dependencies by simply typing in your terminal:
58 |
59 | ````cmd
60 | pip install numpy==1.26 matplotlib ipykernel ipywidgets PyQt5 mitsuba==3.6.0
61 | ````
62 |
63 | Note that `pip install mitsuba` automatically installs Dr. Jit
64 |
65 |
66 |
67 | To use vectorized variants for fast rendering in python, install `NVidia driver >= 495.89` (Windows / Linux) or `LLVM >= 11.1` (Mac OS)
68 |
69 | In Mac OS, you may install llvm just by
70 |
71 | `brew install llvm`
72 |
73 |
74 |
75 | ## Installing troubleshooting
76 |
77 | ### LLVM variant
78 |
79 | https://github.com/NVlabs/sionna/discussions/160
80 |
81 |
82 |
83 | * If `mi.set_variant('llvm_ad_rgb')` works on terminal->python, but not in VSCode
84 |
85 | https://stackoverflow.com/questions/43983718/how-can-i-globally-set-the-path-environment-variable-in-vs-code
86 |
--------------------------------------------------------------------------------
/README_ko.md:
--------------------------------------------------------------------------------
1 | # Rendering Lecture using Mitsuba3
2 | Seminar materials of physically based rendering with tutorials and homework using mitsuba3-python
3 |
4 | Please do not upload your solution to online publicly.
5 |
6 |
7 |
8 | 본 자료는 완벽하지 않거나 작업 갱신이 느릴 수 있다. [Discussion](https://github.com/shinyoung-yi/lecture-rendering-mitsuba/discussions) 페이지를 활용한 모든 **질문, 오류 정정, 컨텐츠 제안, 업데이트에 대한 종용** 등은 언제나 환영된다.
9 |
10 | ## 다른 렌더링 학습 자료와의 차이점
11 |
12 | 물리 기반 렌더링, 대표적으로 광선 추적(ray tracing)은 컴퓨터 그래픽스 및 비전에서 중요한 기반 기술이다. 하지만 복잡한 C++ 코딩, 이론적 난이도 등으로 인해 진입 장벽이 높은 것이 사실이다. 본 리포지토리에 담긴 자료는 렌더링 교육의 진입 장벽을 낮추는 것을 가장 큰 목표로 하고 있으며, 제공된 강의 자료(`./slides/*.pdf`), 튜토리얼(`./tutotial*.ipynb`), 과제(`./hw*.ipynb`)를 잘 따라올 경우 **하루**의 시간 안에 광선 추적 분야의 베이스라인 알고리즘인 경로 추적(path tracing)을 구현할 수 있을 것이라 생각한다.
13 |
14 |
15 |
16 | ### 특징
17 |
18 | 렌더링 학습을 진입 장벽을 최소한으로 낮추기 위해, 본 자료는 여타 자료들과는 다소 다른 특징들이 있다.
19 |
20 | * [Mitsuba 3](https://www.mitsuba-renderer.org/)를 이용하여 Python 코드로만 모든 과정이 이루어짐
21 | * 튜토리얼과 과제가 Jupyter notebook으로 이루어져, 최종 렌더링 이미지를 얻기 전이어도 중간 과정의 변수들을 시각화하여 확인하기 용이함
22 | * 전체적인 광선 추적 알고리즘에 대한 이해를 우선시하여, 광선 추적 내부에서 사용되는 하위레벨의 쿼리들은 선택 학습 요소로 남겨둠.
23 | * 특히, 광선 교차(ray intersection) 쿼리는 본 자료에서 다루지 않음. 쿼리의 목적성이 명료한 데에 반하여 구현이 복잡한데다가 파이썬 구현이 사실상 불가능
24 |
25 |
26 |
27 | ## 학습 순서
28 |
29 | 다음 단원만 학습하면 아주 빠르게 광선 추척 알고리즘에 대한 이해를 얻을 수 있다.
30 |
31 | 1. Radiometry: `./slides/01. radiometry and light transport.pdf`, `./hw1_radiometry.ipynb`
32 | * HW1에서 장면을 구성하는 값에 참조하기 위해 Mitsuba 3의 API를 사용하지만, 각 문제의 스켈레톤 코드에 이미 사용법을 제공하였기에 1단원에서는 `./tutorial*.ipynb` 튜토리얼을 학습하지 않아도 무방
33 | 2. Probability: `./slides/02. probability and statistical inference.pdf`, `./hw2_probability.ipynb`
34 | 3. Path Tracing: `./slides/TBA`, `./tutorial*.ipynb`, `./hw3_pathtracing.ipynb`, `./hw4_pathtracing.ipynb`
35 | * HW3, HW4는 `./tutorial*.ipynb` 튜토리얼을 읽은 후 시작하는 것이 필요
36 |
37 |
38 |
39 | 추후 추가 예정
40 |
41 | 4. Advanced samplings: (제한된 형태의) bidirectional path tracing, 해석적 적분과의 결합, 등 다양한 알고리즘 중 몇 개
42 | 5. Volumetric rendering
43 | 6. Differentiable rendering: detached와 attached sampling, boundary integral과 reparameterization 등 differentiable rendering에만 등장하는 복잡한 방법론들의 전달력 있는 시각화 및 단순한 상황에서 개념을 확실히 구분할 수 있는 쉬운 예제들을 만들면 좋겠다고 생각 중
44 |
45 |
46 |
47 | ## 의존성
48 |
49 | 본 자료에 포함된 코드들을 실행시키기 위해서는 아래와 같은 파이썬(3.11) 패키지가 필요하다.
50 |
51 | * NumPy
52 | * Matplotlib
53 | * mitsuba 3
54 | * Dr. Jit
55 |
56 |
57 | 터미널에 다음 명령을 입력하여 한번에 설치할 수 있다.
58 |
59 | ```cmd
60 | pip install numpy==1.26 matplotlib ipykernel ipywidgets PyQt5 mitsuba==3.6.0
61 | ```
62 |
63 | 처음에 나열된 의존성 중 Dr. Jit `pip install mitsuba`을 실행할 때 자동으로 설치된다.
64 |
65 |
66 |
67 | To use vectorized variants for fast rendering in python, install `NVidia driver >= 495.89` (Windows / Linux) or `LLVM >= 11.1` (Mac OS)
68 |
69 | In Mac OS, you may install llvm just by
70 |
71 | `brew install llvm`
72 |
73 |
74 |
75 | ## Installing troubleshooting
76 |
77 | ### LLVM variant
78 |
79 | https://github.com/NVlabs/sionna/discussions/160
80 |
81 |
82 |
83 | * If `mi.set_variant('llvm_ad_rgb')` works on terminal->python, but not in VSCode
84 |
85 | https://stackoverflow.com/questions/43983718/how-can-i-globally-set-the-path-environment-variable-in-vs-code
86 |
--------------------------------------------------------------------------------
/[talk]/2025postech/Path Tracing One Day Class.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/[talk]/2025postech/Path Tracing One Day Class.pdf
--------------------------------------------------------------------------------
/advanced/hw1_problem1.(e).md:
--------------------------------------------------------------------------------
1 | # Homework 1: Probelm 1. (e) Jacobian determinant of Rusinkiewicz coordinates
2 |
3 | $$
4 | \def\homega{\hat\omega}
5 |
6 | \def\rmd{\mathrm{d}}
7 | \def\rmf{\mathrm{f}}
8 | \def\bfu{\mathbf{u}}
9 | \def\bfB{\mathbf{B}}
10 | \def\bfJ{\mathbf{J}}
11 |
12 | \def\floor#1{\left\lfloor #1 \right\rfloor}
13 | \def\ceil#1{\left\lceil #1 \right\rceil}
14 |
15 | \def\abs#1{\left|{#1}\right|}
16 | \def\pfrac#1#2{\frac{\partial#1}{\partial#2}}
17 | $$
18 |
19 | ***TODO: polish more***
20 |
21 | ## Basic Differential Geometry
22 |
23 | $\phi:U\subset\R^n \to M\subset \R^m$
24 | $$
25 | \int_{V}{ f\left(x,y\right) \rmd M\left(x\right) }
26 | = \int_{f^{-1}\left(V\right)}{f\left(\bfu\right)J\left(\bfu\right)\rmd^n \bfu}
27 | , \\
28 | \text{ where }
29 | J\left(\bfu\right) = \left|\frac{
30 | \rmd M
31 | }{
32 | \rmd^n \bfu
33 | }\right|
34 | = \sqrt{\det \bfJ_\phi^T\bfJ_\phi}
35 | \text{ and }
36 | $$
37 |
38 |
39 |
40 |
41 | A parameterized product manifold of surfaces: $\phi:U\subset\R^4 \to M\times N\subset\R^3\times\R^3$.
42 |
43 | Differential measure:
44 | $$
45 | \int_{V}{ f\left(x,y\right) \rmd M\left(x\right)\rmd N\left(y\right) }
46 | = \int_{f^{-1}\left(V\right)}{f\left(\bfu\right)J\left(\bfu\right)\rmd^4 \bfu}
47 | ,\text{ where }
48 | J\left(\bfu\right) = \left|\frac{
49 | \rmd M\rmd N
50 | }{
51 | \rmd^4 \bfu
52 | }\right|. \\
53 |
54 | \text{Here, } \int_{V}{ f\left(x,y\right) \rmd M\left(x\right)\rmd N\left(y\right) }
55 | = \int_{\Pi_N\left(V\right)}{
56 | \int_{\left\{ x\in M\mid\left(x,y\right)\in V \right\}}{f\left(x,y\right) \rmd M\left(x\right)}
57 | \rmd N\left(y\right)}
58 | $$
59 |
60 | $$
61 | \left(\bfJ_\phi^T\bfJ_{\phi}\right)_{ij}\left(\bfu\right) = \pfrac{\mathbf x}{u_i}\cdot \pfrac{\mathbf x}{u_j} + \pfrac{\mathbf y}{u_i}\cdot \pfrac{\mathbf y}{u_j}
62 | $$
63 |
64 |
65 |
66 | ## Intermediate condition
67 |
68 | $M^n,N^n \subset \R^{n+1}$
69 |
70 | For any $R \in G$, $R\left(M\right)=M$ and $R\left(N\right)=N$, where $G\subset GL\left(n+1\right)$ is a 1-dim. matrix group.
71 |
72 | Partial coordinates $\bfu_{1:2n-1}:M\times N\to \R^{2n-1}$, where $\bfu\left(x,y\right)=\bfu\left(x',y'\right)$ then $\left(x',y'\right)=R\left(x,y\right)$ for some $R\in G$.
73 |
74 | #### **Proposition.** Suppose that there are two (total) coordinate systems $\bfu$ and $\bfu'$ which extending the partial coordinates $\bfu_{1:3}$. Then the Jacobians of the induced parameterizations satisfies $R\bfJ_{\bfu^{-1}}=\bfJ_{\bfu'^{-1}}$ for some $R\in G$.
75 |
76 | > By supposition:
77 | > $$
78 | > \bfu:M\times N \to U \subset \R^{2n}, \\
79 | > \bfu':M\times N \to U' \subset \R^{2n}.
80 | > $$
81 | > By the property of the partial coordinates:
82 | > $$
83 | > \forall \left(x,y\right)\in M\times N,\ \bfu'^{-1}\circ\bfu\left(x,y\right) = R_\left(x,y\right)\left(x,y\right)
84 | > $$
85 | >
86 |
87 | $$
88 | \int_{M\times N}{f\left(\bfu_{1:2n-1}\right)\rmd M\rmd N} = \int_{U}{f\left( \bfu_{1:2n-1}\right) \left|\bfJ_{\bfu^{-1}}\right|\rmd^{2n}\bfu}
89 | $$
90 |
91 |
92 |
93 | ## Rusinkiewicz Jacobian (differential measure)
94 |
95 | $$
96 | \mathrm{sph}\left(\theta,\phi\right) = \mathrm{sph}\left(\theta,\phi;\hat x,\hat y, \hat z\right) = \sin\theta\cos\phi\hat x+\sin\theta\sin\phi\hat y+\cos\theta\hat z, \\
97 | \homega_h = \left(\sin\theta_h \cos\phi_h,\sin\theta_h\sin\phi_h, \cos\theta_h\right), \\
98 | \homega_{i|o} = \cos\theta_d\homega_h \pm \sin\theta_d\cos\phi_d\left(\cos\theta_h\cos\phi_h,\cos\theta_h\sin\phi_h,-\sin\theta_h\right) \pm \sin\theta_d\sin\phi_d\left( -\sin\phi_h,\cos\phi_h,0 \right) \\
99 | = \cos\theta_d\homega_h \pm \sin\theta_d\cos\phi_d\hat\theta_h \pm \sin\theta_d\sin\phi_d\hat\phi_h, \\
100 |
101 | \pfrac{\homega_{i|o}}{\theta_h} = \mp \sin\theta_d\cos\phi_d\homega_h+\cos\theta_d \hat\theta_h +0, \\
102 |
103 | \pfrac{\homega_{i|o}}{\phi_h} = \left(\sin\theta_h \cos\theta_d \pm \cos\theta_h\sin\theta_d\cos\phi_d \right)\hat\phi_h \mp \sin\theta_d\sin\phi_d\left(\cos\phi_h,\sin\phi_h,0\right), \\
104 |
105 | \pfrac{\homega_{i|o}}{\theta_d} = -\sin\theta_d\homega_h \pm \cos\theta_d\cos\phi_d\hat\theta_h \pm \cos\theta_d\sin\phi_d\hat\phi_h, \\
106 |
107 | \pfrac{\homega_{i|o}}{\phi_d} = 0\mp \sin\theta_d\sin\phi_d\hat\theta_h \pm \sin\theta_d\cos\phi_d\hat\phi_h
108 | $$
109 |
110 | > ### Rusinkiewicz to spherical coordinates of two directions
111 | >
112 | > $$
113 | > \cos\theta_{i|o} = \cos\theta_h\cos\theta_d \mp\sin\theta_h\sin\theta_d\cos\phi_d, \\
114 | > \tan\phi_{i|o} = \frac{
115 | > \sin\theta_h\sin\phi_h\cos\theta_d\pm\cos\theta_h\sin\phi_h\sin\theta_d\cos\phi_d \pm \cos\phi_h\sin\theta_d\sin\phi_d
116 | > }{
117 | > \sin\theta_h\cos\phi_h\cos\theta_d\pm\cos\theta_h\cos\phi_h\sin\theta_d\cos\phi_d \mp \sin\phi_h\sin\theta_d\sin\phi_d
118 | > } \\
119 | >
120 | > \mathrm{numer}^2+\mathrm{denom}^2 = \sin^2\theta_h\cos^2\theta_d+\cos^2\theta_h\sin^2\theta_d\cos^2\phi_d+\sin^2\theta_d\sin^2\phi_d \pm 4\sin\cos\theta_h\sin\cos\theta_d\cos\phi_d \\
121 | > = \sin^2\theta_h\left(\cos^2\theta_d -\sin^2\theta_d\cos^2\phi_d\right)+\sin^2\theta_d \pm 4\sin\cos\theta_h\sin\cos\theta_d\cos\phi_d \eqqcolon r_{i|o}^2 \\
122 | >
123 | > r_ir_o = \sqrt{
124 | > \left[\sin^2\theta_h\left(\cos^2\theta_d-\sin^2\theta_d\cos^2\phi_d\right)+\sin^2\theta_d\right]^2 - 16\sin^2\cos^2\theta_h \sin^2\cos^2\theta_d\cos^2\phi_d
125 | > }\\
126 | >
127 | > \sin\left(\phi_i-\phi_o\right) = \sin\phi_i\cos\phi_o-\cos\phi_i\sin\phi_o = \frac{1}{r_ir_o}\left[
128 | > 2 \sin\theta_h \sin\cos\theta_d \sin\phi_d
129 | > \right], \\
130 | > \cos\left(\phi_i-\phi_o\right) = \cos\phi_i\cos\phi_o+\sin\phi_i\sin\phi_o = \frac1{r_ir_o}\left[ \sin^2\theta_h\cos^2\theta_d - \cos^2\theta_h\sin^2\theta_d\cos^2\phi_d-\sin^2\theta_d\sin^2\phi_d \right] \\
131 | > = \frac1{r_ir_o}\left[
132 | > \sin^2\theta_h\left( \cos^2\theta_d + \sin^2\theta_d \cos^2\phi_d \right) - \sin^2\theta_d
133 | > \right], \\
134 | > = \frac1{r_ir_o}\left[
135 | > \sin^2\theta_h\left( 1 - \sin^2\theta_d \sin^2\phi_d \right) - \sin^2\theta_d
136 | > \right]
137 | > $$
138 | >
139 | > when $\homega_i$ denotes material to light direction.
140 |
141 | > When $\theta_d=\frac\pi2$:
142 | > $$
143 | > \homega_{i|o}=\pm \cos\phi_p \left(\cos\theta_h\cos\phi_h,\cos\theta_h\sin\phi_h,-\sin\theta_h \right) \pm \sin\phi_d\left(-\sin\phi_h,\cos\phi_h,0\right)
144 | > $$
145 | > Note that
146 | > $$
147 | > x_{i|o}^2 + y_{i|o}^2 =\cos^2\phi_p\cos^2\theta_h+\sin^2\phi_d = 1 - \sin^2\theta_h\cos^2\phi_p
148 | > $$
149 | >
150 | >
151 | > Suppose that there is $\theta_h'$, $\phi_h'$, and $\phi_d'$ such that $\homega_{i|o}=\homega_{i|o}'$ (equal both $i$ and $o$).
152 | > $$
153 | > \sin\theta_h\cos\phi_p = \sin\theta_h' \cos\phi_p', \\
154 | > \mathrm{or}\cases{1\\1}
155 | > $$
156 | >
157 | >
158 | >
159 | > \sin\theta_h\cos\phi_p
160 | > }{
161 | > \sin\theta_h'
162 | > }\right) \\
163 |
164 | Note that:
165 | $$
166 | \homega_h\cdot \left(\cos\phi_h,\sin\phi_h,0\right)=\sin\theta_h, \\
167 | \hat \theta_h \cdot\left(\cos\phi_h,\sin\phi_h,0\right)=\cos\theta_h,\\ \hat\phi_h\cdot\left(\cos\phi_h,\sin\phi_h,0\right)=0
168 | $$
169 | $$
170 | \bfJ_{\homega_{i|o}}^T \bfJ_{\homega_{i|o}} = 2\pmatrix{
171 | \sin^2\theta_d\cos^2\phi_d + \cos^2\theta_d & \sin\theta_h\sin^2\theta_d\sin\cos\phi_d & 0 & 0 \\
172 | \sin\theta_h\sin^2\theta_d\sin\cos\phi_d & \sin^2\theta_h\left(\cos^2\theta_d-\sin^2\theta_d\cos^2\phi_d\right) + \sin^2\theta_d & 0 & \cos\theta_h \sin^2\theta_d \\
173 | 0 & 0 & 1 & 0 \\
174 | 0 & \cos\theta_h\sin^2\theta_d & 0 & \sin^2\theta_d
175 | }
176 | $$
177 |
178 | $$
179 | \frac1{16} \det \bfJ^T\bfJ
180 | = \left(\sin^2\theta_d\cos^2\phi_d+\cos^2\theta_d\right) \sin^2\theta_d\left[ \sin^2\theta_h\left(1-\sin^2\theta_d\cos^2\phi_d\right) \right]
181 | - \sin^2\theta_h\sin^6\theta_d\sin^2\cos^2\phi_d \\
182 |
183 | = \sin^2\theta_h\sin^2\theta_d\left[
184 | \left(\sin^2\theta_d\cos^2\phi_d+\cos^2\theta_d\right)\left(1-\sin^2\theta_d\cos^2\phi_d\right) - \sin^4\theta_d\sin^2\cos^2\phi_d
185 | \right] \\
186 |
187 | = \sin^2\theta_h\sin^2\theta_d\cos^2\theta_d, \\
188 |
189 | \therefore\ J_{\mathrm{Rus}} \coloneqq \sqrt{\det \bfJ^T\bfJ} = 4\left|\sin\theta_h\sin\theta_d\cos\theta_d\right|
190 | $$
191 |
192 | $$
193 | 16\pi^2 = \int_{S^2\times S^2}{\rmd \homega_i\rmd \homega_o}
194 | = \int_{\left[0,\pi\right]\times\left[0,2\pi\right]\times\left[0,\frac\pi 2\right]\times\left[0,2\pi\right]}{4\left|\sin\theta_h\sin\theta_d\cos\theta_d\right|\rmd\left(\theta_h,\phi_h,\theta_d,\phi_d\right)} \\
195 | = 8\pi \int_{\left[0,\pi\right]\times\left[0,\frac\pi 2\right]\times\left[0,2\pi\right]}{\left|\sin\theta_h\sin\theta_d\cos\theta_d\right|\rmd\left(\theta_h,\theta_d,\phi_d\right)}
196 | = 4\times2\times 2\pi \times \frac12 \times 2\pi = 16\pi^2
197 | $$
198 |
--------------------------------------------------------------------------------
/figsrc/cbox_max2_spp1024.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/cbox_max2_spp1024.exr
--------------------------------------------------------------------------------
/figsrc/cbox_max2_spp4096.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/cbox_max2_spp4096.exr
--------------------------------------------------------------------------------
/figsrc/cbox_max8_rr5_spp4096.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/cbox_max8_rr5_spp4096.exr
--------------------------------------------------------------------------------
/figsrc/cbox_max8_rr5_spp4096_seed0.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/cbox_max8_rr5_spp4096_seed0.exr
--------------------------------------------------------------------------------
/figsrc/cbox_max8_rr5_spp4096_seed1.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/cbox_max8_rr5_spp4096_seed1.exr
--------------------------------------------------------------------------------
/figsrc/cbox_max8_rr5_spp4096_seed2.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/cbox_max8_rr5_spp4096_seed2.exr
--------------------------------------------------------------------------------
/figsrc/cbox_max8_rr5_spp4096_seed3.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/cbox_max8_rr5_spp4096_seed3.exr
--------------------------------------------------------------------------------
/figsrc/cbox_max8_rr5_spp4096_seed4.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/cbox_max8_rr5_spp4096_seed4.exr
--------------------------------------------------------------------------------
/figsrc/figgen.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import mitsuba as mi\n",
10 | "\n",
11 | "mi.set_variant('cuda_ad_rgb')"
12 | ]
13 | },
14 | {
15 | "cell_type": "markdown",
16 | "metadata": {},
17 | "source": [
18 | "## Cornell Box GT"
19 | ]
20 | },
21 | {
22 | "cell_type": "code",
23 | "execution_count": 2,
24 | "metadata": {},
25 | "outputs": [],
26 | "source": [
27 | "scene = mi.load_dict(mi.cornell_box())\n",
28 | "spp = 4096\n",
29 | "for seed in range(5):\n",
30 | " img = mi.render(scene, spp=spp, seed=seed)\n",
31 | " mi.Bitmap(img).write(f\"cbox_max{8}_rr{5}_spp{spp}_seed{seed}.exr\")"
32 | ]
33 | },
34 | {
35 | "cell_type": "code",
36 | "execution_count": 35,
37 | "metadata": {},
38 | "outputs": [],
39 | "source": [
40 | "ptint_depth2 = mi.load_dict({'type': 'path', 'max_depth': 2})\n",
41 | "img = mi.render(scene, spp=spp, integrator=ptint_depth2)\n",
42 | "mi.Bitmap(img).write(f\"cbox_max{2}_spp{spp}.exr\")"
43 | ]
44 | },
45 | {
46 | "cell_type": "markdown",
47 | "metadata": {},
48 | "source": [
49 | "## Veach MIS"
50 | ]
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": 32,
55 | "metadata": {},
56 | "outputs": [],
57 | "source": [
58 | "scene = mi.load_file(\"../scene/veach-mis/scene.xml\")\n",
59 | "spp = 1024\n",
60 | "img = mi.render(scene, spp=spp)\n",
61 | "mi.Bitmap(img).write(f\"veachmis_max{8}_rr{5}_spp{spp}.exr\")"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": 34,
67 | "metadata": {},
68 | "outputs": [],
69 | "source": [
70 | "img = mi.render(scene, spp=spp, integrator=ptint_depth2)\n",
71 | "mi.Bitmap(img).write(f\"veachmis_max{2}_spp{spp}.exr\")"
72 | ]
73 | }
74 | ],
75 | "metadata": {
76 | "kernelspec": {
77 | "display_name": "polarsh",
78 | "language": "python",
79 | "name": "python3"
80 | },
81 | "language_info": {
82 | "codemirror_mode": {
83 | "name": "ipython",
84 | "version": 3
85 | },
86 | "file_extension": ".py",
87 | "mimetype": "text/x-python",
88 | "name": "python",
89 | "nbconvert_exporter": "python",
90 | "pygments_lexer": "ipython3",
91 | "version": "3.11.7"
92 | },
93 | "orig_nbformat": 4
94 | },
95 | "nbformat": 4,
96 | "nbformat_minor": 2
97 | }
98 |
--------------------------------------------------------------------------------
/figsrc/veachmis_max2_spp1024.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/veachmis_max2_spp1024.exr
--------------------------------------------------------------------------------
/figsrc/veachmis_max8_rr5_spp1024.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/figsrc/veachmis_max8_rr5_spp1024.exr
--------------------------------------------------------------------------------
/lecturenote/01. radiometry and light transport.md:
--------------------------------------------------------------------------------
1 | # Lecture 01. Radiometry and Light Transport
2 |
3 | $$
4 | \def\rmd{\mathrm{d}}
5 |
6 | \def\bfp{\mathbf{p}}
7 | \def\bfq{\mathbf{q}}
8 | \def\bbS{\mathbb{S}}
9 |
10 | \def\hn{\hat{n}}
11 | \def\homega{\hat{\omega}}
12 | \def\homegasph{\hat\omega_{\mathrm{sph}}}
13 |
14 | \def\subsetsurf{\underset{\mathrm{surf}}{\subset}}
15 |
16 | \def\abs#1{\left|#1\right|}
17 | \def\norm#1{\left\|#1\right\|}
18 |
19 | \def\difrac#1#2{\frac{\rmd{#1}}{\rmd{#2}}}
20 | \def\pfrac#1#2{\frac{\partial{#1}}{\partial{#2}}}
21 | $$
22 |
23 | ## 0. Mathematical preliminaries
24 |
25 | ### Sphere: the set of all directions
26 |
27 | $$
28 | \bbS^2\coloneqq \left\{\bfp\in\R^3 \mid \norm{\bfp}=1\right\}
29 | $$
30 |
31 | **Spherical coordinates**:
32 | $$
33 | \homegasph\left(\theta,\phi\right) \coloneqq \begin{bmatrix}
34 | \sin\theta\cos\phi \\ \sin\theta\sin\phi \\ \cos\theta
35 | \end{bmatrix}
36 | $$
37 |
38 | Given unit vector $\hn\in\bbS^2$, **hemispheres** are defined as $\bbS_{\hn}^2 \coloneqq\left\{\homega \in \bbS^2 \mid \hn\cdot\homega \ge 0\right\}$. If a coordinate system is given in a context, we write $\bbS_\pm^2 \coloneqq \bbS_{\pm \hat z}^2$, respectively.
39 |
40 | For $\bfp,\bfq\in\R^3$, $\homega_{\bfp\bfq}\coloneqq \frac{\bfq - \bfp}{\norm{\bfq-\bfp}}\in\bbS^2$
41 |
42 |
43 |
44 | ### Measure and integration on the sphere
45 |
46 | Suppose that there are a spherical function $f:\bbS^2\to \R^k$ and a spherical set (solid angle region) $\Omega\subset \bbS^2$. The **solid angle** (solid angle measure) of $\Omega$ is defined by:
47 | $$
48 | \abs{\Omega} \coloneqq \int_{\homegasph^{-1}\left(\Omega\right)}{\sin\theta \rmd \theta\rmd \phi}.
49 | $$
50 | Then the **integral** of $f$ over $\Omega$ is evaluated as follows:
51 | $$
52 | \int_\Omega {f\left(\homega\right)\rmd \homega} = \int_{\homegasph^{-1}\left(\Omega\right)}{f\left(\theta,\phi\right) \sin\theta \rmd \theta \rmd\phi}.
53 | $$
54 |
55 | Here, $f\left(\theta,\phi\right)$ should be written as $f\left(\homegasph^{-1}\left(\theta,\phi\right)\right)$ in a strict manner, but we usually use the shorter one for the sake of simplicity.
56 |
57 |
58 |
59 | ### Projection
60 |
61 | For a set $V\subset \R^3$ (volume, surface, or any other set), the **(projected) solid angle** of $V$ **with respect to** $\bfp\in\R^3$ is defined as follows:
62 | $$
63 | \abs{\Pi_\bfp\left(V\right)} = \abs{\left\{\homega_{\bfp\bfq}\in\bbS^2\mid \bfq\in V\right\}}
64 | $$
65 |
66 | The **projection** of $\bfp\in\R^3$ **on a surface** $S\subsetsurf\R^3$ **along direction** $\homega\in\bbS^2$ is defined as:
67 | $$
68 | \Pi_{\bfp\to S}\left(\homega\right) \coloneqq \bfp+\left(\min\left\{t\in \R_+ \mid \bfp+t\homega\in S \right\}\right)\homega
69 | $$
70 |
71 |
72 | ### Surface
73 |
74 | If a set $S\subset \R^3$ is a **surface**, then we write $S\subsetsurf \R^3$. We call $S$ is a **oriented surface** if there is a continuous function of **surface normal** vectors $\hn_S:S\to \bbS^2$.
75 |
76 |
77 |
78 |
79 | ## 1. Radiometric quantities
80 |
81 | Here we define main physical quantities of radiometry. We first define radiant energy as a function of a subset of $\R^3\times \bbS^2$ and then define other quantities using the radiant energy. After defining all quantities, we will also investigate that we can define radiance first and define other quantities using the definition of radiance.
82 |
83 |
84 |
85 | ### Radiant energy
86 |
87 | ##### **Quantity.** A radiant energy $Q\left(A, \left[a, b\right]\right)$ is a quantity defined on a set $A\subset \R^3\times \bbS^2$ and an interval $\left[a,b\right]\subset \R$, where $S\coloneqq \left\{\bfp\mid \left(\bfp,\homega\right) \in A\right\}\subsetsurf \R^3$ which satisfy:
88 |
89 | $$
90 | \text{If }&A\cap B = \empty &\text{ then }& Q\left(A\cup B, \left[a,b\right]\right) &= Q\left(A, \left[a,b\right]\right) + Q\left(B, \left[a,b\right]\right), \\
91 | \text{If }&a\le b \le c &\text{ then }& Q\left(A, \left[a,c\right]\right) &= Q\left(A, \left[a,b\right]\right) + Q\left(A, \left[b, c\right]\right).
92 | $$
93 |
94 | > **Convention.** For given oriented surface $S$, the radiance energy on the surface $S$ on a time interval $\left[a,b\right]$ can be defined as follows.
95 | > $$
96 | > Q\left(S,\left[a,b\right]\right) \coloneqq Q\left(\left\{\left(\bfp,\homega\right)\mid \bfp\in S,\ \homega\in \bbS_{\pm \hn_S\left(\bfp\right)}^2 \right\}, \left[a,b\right]\right).
97 | > $$
98 | > Here, we can take one of sign of $\pm \hn_S\left(\bfp\right)$ depending on a context.
99 |
100 |
101 |
102 | ### Radiant power
103 |
104 | ##### **Definition.** The radiant flux (radiant power) of a set $A\subset \R^3\times \bbS^2$ and $t\in \R$, where $S\coloneqq \left\{\bfp\mid \left(\bfp,\homega\right) \in A\right\}\subsetsurf \R^3$, is defined as follows:
105 |
106 | $$
107 | \Phi \left(A, t\right) \coloneqq \difrac{} {t}Q\left(A, \left[a,t\right]\right)
108 | $$
109 |
110 | > **Well-definedness.** The radiant flux is well defined independent of $a$.
111 | >
112 | > **Property.** If $A\cap B = \empty$ then $\Phi\left(A\cup B, t\right) = \Phi\left(A, t\right) + \Phi\left(B, t\right)$.
113 | >
114 | > **Convention.** Similar to radiant energy, the radiant flux on given oriented surface $S$ and a time $t$ is defined as follows.
115 | > $$
116 | > \Phi\left(S,t\right) \coloneqq \Phi\left(\left\{\left(\bfp,\homega\right)\mid \bfp\in S,\ \homega\in \bbS_{\pm \hn_S\left(\bfp\right)}^2 \right\}, t\right).
117 | > $$
118 | > **Convention.** We usually consider a static (or steady-state) scenario with a fixed time $t$. Then we usually omit the time dependency as $\Phi\left(A\right)$ or $\Phi\left(S\right)$
119 |
120 | ##### **Proposition.** The radiant energy $Q\left(A,\left[a,b\right]\right)$ can be evaluated using radiant fluxes $\Phi$ as follows.
121 |
122 | $$
123 | Q\left(A,\left[a,b\right]\right) = \int_a^b {\Phi\left(A,t\right)\rmd t}
124 | $$
125 |
126 |
127 |
128 | ### Radiant intensity
129 |
130 | ##### **Definition.** The radiant intensity of a point source at $\bfp\in\R^3$ along $\homega\in\bbS^2$ is defined as follows:
131 |
132 | $$
133 | I\left(\homega\right) \coloneqq \lim_{\abs{\Pi_\bfp\left(S\right)}\to 0,\ S\subsetsurf \R^3} \frac{\Phi\left(S\right)}{\abs{\Pi_\bfp\left(S\right)}},
134 | $$
135 |
136 | with taking the surface normal direction of $S$ to be outward from $\bfp$.
137 |
138 |
139 |
140 | ### Irradiance
141 |
142 | ##### **Definition.** Given surface $S\subsetsurf\R^3$, the irradiance at $p\in S$ and a solid angle region $\Omega\subset\bbS^2$ is defined as follows:
143 |
144 | $$
145 | E_S\left(\bfp,\Omega\right) \coloneqq \lim_{\abs{A}\to 0,\ p\in A\subset S} \frac{\Phi\left(A\times \Omega\right)}{\abs{A}}
146 | $$
147 |
148 | > **Property.** If $\Omega_1\cap\Omega_2 = \empty$ then $E_S\left(\bfp,\Omega_1\cup \Omega_2\right) = E_S\left(\bfp,\Omega_1\right)+ E_S\left(\bfp,\Omega_2\right)$.
149 | >
150 | > **Convention.**
151 | >
152 | > * The irradiance at $\bfp\in \R^3$ and a solid angle region $\Omega\subset\bbS^2$ with respect to the normal direction $\hn\in\bbS^2$ is defined as $E_\hn\left(\bfp,\Omega\right)\coloneqq E_{S_{\hn}}\left(\bfp,\Omega\right)$ where $S_\hn$ denotes any oriented surface with a normal $\hn$ at $\bfp\in S$.
153 | > * Simply writing $E_{S}\left(\bfp\right)$ usually denotes $E_S\left(\bfp, \bbS_{\hn}^2\right)$, or sometimes $E_S\left(\bfp, \bbS_{-\hn}^2\right)$ or $E_S\left(\bfp, \bbS^2\right)$.
154 |
155 | ##### **Proposition.** The radiant flux $\Phi\left(A\right)$ can be evaluated using irradiances as follows.
156 |
157 | $$
158 | \Phi\left(A\right) = \int_{S}{E_S\left(\bfp, \left\{\homega\in \bbS^2 \mid \left(\bfp,\homega\right) \in A\right\}\right)\rmd\bfp}, \text{ where } S\coloneqq \left\{\bfp\mid \left(\bfp,\homega\right) \in A\right\}\subsetsurf \R^3
159 | $$
160 |
161 | ##### **Proposition.**
162 |
163 | >
164 |
165 | ##### **Proposition.** Suppose that there is a point source at $\bfp\in \R^3$. Given surface $S\subsetsurf\R^3$, the irradiance at $\bfq\in S$ is evaluated as follows:
166 |
167 | $$
168 | E_S\left(\bfq\right) = \frac{I\left(\homega_{\bfp\bfq}\right)\abs{\hn_S\left(\bfq\right)\cdot \homega_{\bfp\bfq}}}{\norm{\bfq-\bfp}^2}
169 | $$
170 |
171 |
172 |
173 | ### Radiance
174 |
175 | ##### **Definition.** The radiance at $\bfp\in\R^3$ along $\homega\in\bbS^2$ is defined as follows:
176 |
177 | $$
178 | L\left(\bfp,\homega\right) \coloneqq \lim_{\abs{\Omega}\to 0,\ \homega\in \Omega\subset\bbS^2} \frac{E_{\homega}\left(\bfp, \Omega\right)}{\abs{\Omega}} = \lim_{\abs{\Omega}\to0,\ \homega\in\Omega\subset\bbS^2} \frac{1}{\abs{\hn_S\left(\bfp\right)\cdot \homega}}\frac{E_S\left(\bfp,\Omega\right)}{\abs{\Omega}}, \text{ with any choice of }\bfp\in S\subsetsurf\R^3
179 | $$
180 |
181 | ##### **Proposition.** The irradiance $E_S\left(p,\Omega\right)$ can be evaluated using radiances as follows:
182 |
183 | $$
184 | E_S\left(\bfp,\Omega\right) = \int_\Omega {L\left(\bfp,\homega\right)\abs{\hn_S\left(\bfp\right) \cdot \homega}\rmd \homega}
185 | $$
186 |
187 | ##### **Proposition.** The radiant intensity $I\left(\homega\right)$ of a point source at $\bfp\in\R^3$ can be described as Dirac delta radiances as follows.
188 |
189 | $$
190 | L\left(\bfq,\homega\right) = \frac{I\left(\homega\right)}{\norm{\bfq-\bfp}^2}\delta_{\bbS^2}\left(\homega, \homega_{\bfp\bfq}\right)
191 | $$
192 |
193 | > **Proof.**
194 | > $$
195 | > E_S\left(\bfq,\Omega\right) = \int_\Omega{L\left(\bfq,\homega\right)\abs{\hn_S\left(\bfq\right)\cdot\homega}\rmd\homega} = \int_\Omega{\frac{I\left(\homega\right)}{\norm{\bfq-\bfp}^2}\delta_{\bbS^2}\left(\homega, \homega_{\bfp\bfq}\right)\abs{\hn_S\left(\bfq\right)\cdot\homega}\rmd\homega} = \frac{I\left(\homega_{\bfp\bfq}\right)\abs{\hn_S\left(\bfq\right)\cdot \homega_{\bfp\bfq}}}{\norm{\bfq-\bfp}^2}
196 | > $$
197 | > **Alternative.** For any surface $S\subsetsurf\R^3$ with $\bfp\in S$ and $\hn_S\left(\bfp\right)\not\perp \bfq-\bfp $,
198 | > $$
199 | > L\left(\bfq,\homega\right) = \frac{I\left(\homega\right)}{\abs{\hn_S\left(\bfp\right)\cdot \homega_{}}} \delta_S\left(\Pi_{\bfq\to S}\left(-\homega\right), \bfp\right)
200 | > $$
201 |
202 |
203 |
204 | ## 2. Energy conservation and light transport
205 |
206 | ##### **Proposition.** The following two laws are equivalent:
207 |
208 | * **Energy conservation:** For $A$ and $B\subset \R^3 \times \bbS^2$, $\Phi\left(A\right) = \Phi\left(B\right)$ holds whenever following two conditions hold:
209 | $$
210 | \forall \left(\bfp,\homega\right)\in A, \quad \exists! \bfq\in \left\{\bfq'\mid \left(\bfq',\homega'\right)\in B\right\} \text{ s.t. } \bfp-\bfq\parallel \pm \homega \quad \text{and} \quad \text{there is no source between $\bfp$ and $\bfq$.} \\
211 | \forall \left(\bfp,\homega\right)\in B, \quad \exists! \bfq\in \left\{\bfq'\mid \left(\bfq',\homega'\right)\in A\right\} \text{ s.t. } \bfp-\bfq\parallel \pm \homega \quad \text{and} \quad \text{there is no source between $\bfp$ and $\bfq$.}
212 | $$
213 |
214 | * **Light transport**: $L\left(\bfp, \homega\right) = L\left(\bfp+t\homega,\homega\right)$ for any $t\in\R$ whenever there is no source between $\bfp$ and $\bfp+t\homega$
215 |
216 | > **Proof of ($\Longrightarrow$)**
217 | >
218 | > Let $S_0$ and $S_t$ be surfaces containing $\bfp$ and $\bfp+t\homega$, respectively, and $\hn_{S_0}\left(\bfp\right)=\hn_{S_t}\left(\bfp+t\homega\right)=\homega$. Consider a sequence of such surfaces $S_0$ and $S_t$ with such conditions and decreasing their areas.
219 | > $$
220 | > L\left(\bfp,\homega\right)
221 | > = \lim_{\abs{S_t}\to0} \frac{
222 | > E\left(\bfp,\Pi_{\bfp}\left(S_t\right)\right)
223 | > }{
224 | > \abs{\Pi_{\bfp}\left(S_t\right)}
225 | > }
226 | > = \lim_{\abs{S_t}\to0}\lim_{\abs{S_0}\to0} \frac{
227 | > \Phi\left(\bigcup_{\bfq\in S_0} \left\{ \bfq \right\} \times \Pi_{\bfq} \left(S_t\right) \right)
228 | > }{
229 | > \abs{S_0}\abs{\Pi_{\bfp}\left(S_t\right)}
230 | > }.
231 | > $$
232 | > Here, $\bigcup_{\bfq\in S_0} \left\{ \bfq \right\} \times \Pi_{\bfq} \left(S_t\right) = \bigcup_{\bfq\in S_t} \left\{ \bfq \right\} \times -\Pi_{\bfq} \left(S_0\right)$ and
233 | > $$
234 | > \Phi\left(\bigcup_{\bfq\in S_0} \left\{ \bfq \right\} \times \Pi_{\bfq} \left(S_t\right) \right) = \Phi\left(\bigcup_{\bfq\in S_t} \left\{ \bfq \right\} \times -\Pi_{\bfq} \left(S_0\right) \right) = \int_{S_t}{E\left(\bfq, -\Pi_\bfq\left(S_0\right)\right) \rmd \bfq}
235 | > $$
236 | >
237 |
238 |
239 |
240 | ## 3. Comparison with Coulomb-Guass laws
241 |
242 | Gravitation and static electric force.
243 |
244 |
--------------------------------------------------------------------------------
/scene/veach-mis.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/scene/veach-mis.zip
--------------------------------------------------------------------------------
/scene/veach-mis/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/scene/veach-mis/.DS_Store
--------------------------------------------------------------------------------
/scene/veach-mis/LICENSE.txt:
--------------------------------------------------------------------------------
1 | This is the Mitsuba version of 'Veach, MIS' by Benedikt Bitterli, downloaded from https://benedikt-bitterli.me/resources/
2 |
3 | The original file may be obtained here: https://benedikt-bitterli.me/resources
4 |
5 | This scene was released under a CC0 license and is in the public domain.
6 | It may be copied, modified and used commercially, without permission or
7 | attribution. However, crediting the artist is encouraged.
8 |
9 | For more information about the license, please see
10 | https://creativecommons.org/publicdomain/zero/1.0/
11 |
--------------------------------------------------------------------------------
/scene/veach-mis/TungstenRender.exr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/scene/veach-mis/TungstenRender.exr
--------------------------------------------------------------------------------
/scene/veach-mis/TungstenRender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/scene/veach-mis/TungstenRender.png
--------------------------------------------------------------------------------
/scene/veach-mis/scene.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/scene/veach-mis/scene_delta.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/scene/veach-mis/scene_envmap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/slides/01. radiometry and light transport.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/slides/01. radiometry and light transport.pdf
--------------------------------------------------------------------------------
/slides/02. probability and statistical inference.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/slides/02. probability and statistical inference.pdf
--------------------------------------------------------------------------------
/slides/03. Path tracing.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinyoung-yi/lecture-rendering-mitsuba/a96868921c2352b64369e43b1f8e32c274912289/slides/03. Path tracing.pdf
--------------------------------------------------------------------------------
/tutorial1_Sampler.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# A quickstart for Mitsuba building blocks"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 1,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "import drjit as dr\n",
17 | "import mitsuba as mi"
18 | ]
19 | },
20 | {
21 | "cell_type": "markdown",
22 | "metadata": {},
23 | "source": [
24 | "## 1. Random number generator: `mi.Sampler` class\n",
25 | "A `mi.Sampler` instance can be easily understood as a random number generator.\n",
26 | "`sampler.next_1d()` is similar to `np.random.rand()`, and `sampler.next_2d()` is similar to `np.random.rand(2)`, when `sampler` is an instance of `mi.Sampler` class.\n",
27 | "\n",
28 | "Note that the resulting array shape for `sampler.next_1d()` and `sampler.next_2d()` are slightly depends on whether the Mitsuba variant is scalar or vectorized (CUDA or LLVM)."
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": 2,
34 | "metadata": {},
35 | "outputs": [
36 | {
37 | "name": "stdout",
38 | "output_type": "stream",
39 | "text": [
40 | "#---------- scalar_rgb ----------\n",
41 | "type(sampler) = \n",
42 | "IndependentSampler[\n",
43 | " base_seed = 0\n",
44 | " sample_count = 4\n",
45 | " samples_per_wavefront = 1\n",
46 | " wavefront_size = 0\n",
47 | "]\n",
48 | "sampler.next_1d() = 0.10837864875793457 : \n",
49 | "sampler.next_2d() = [0.90696, 0.406692] : \n",
50 | "\n",
51 | "#---------- cuda_ad_rgb ----------\n",
52 | "type(sampler) = \n",
53 | "IndependentSampler[\n",
54 | " base_seed = 0\n",
55 | " sample_count = 4\n",
56 | " samples_per_wavefront = 1\n",
57 | " wavefront_size = 0\n",
58 | "]\n",
59 | "sampler.next_1d() = [0.108379] : \n",
60 | "sampler.next_2d() = [[0.90696, 0.406692]] : \n"
61 | ]
62 | }
63 | ],
64 | "source": [
65 | "def test_sampler():\n",
66 | " sampler = mi.load_dict({'type': 'independent'})\n",
67 | " print(f\"{type(sampler) = }\")\n",
68 | " assert isinstance(sampler, mi.Sampler)\n",
69 | " print(sampler) # This shows some attributes for `sampler`,\n",
70 | " # but we have not to care about any of them now.\n",
71 | "\n",
72 | " val = sampler.next_1d()\n",
73 | " assert isinstance(val, mi.Float)\n",
74 | " print(f\"sampler.next_1d() = {val} : {type(val)}\")\n",
75 | " val = sampler.next_2d()\n",
76 | " assert isinstance(val, mi.Point2f)\n",
77 | " print(f\"sampler.next_2d() = {val} : {type(val)}\")\n",
78 | "\n",
79 | "mi.set_variant('scalar_rgb')\n",
80 | "print(f\"#---------- {mi.variant()} ----------\")\n",
81 | "test_sampler()\n",
82 | "\n",
83 | "mi.set_variant('cuda_ad_rgb', 'llvm_ad_rgb')\n",
84 | "print(f\"\\n#---------- {mi.variant()} ----------\")\n",
85 | "test_sampler()"
86 | ]
87 | },
88 | {
89 | "cell_type": "markdown",
90 | "metadata": {},
91 | "source": [
92 | "Note that common research-oriented renderers including Mitsuba uses the deterministic pseudo-random number generator PCG32. Thus, the same seed and the number of call of `next_1d()` and `next_2d()` yields the same results.\n",
93 | "\n",
94 | "See more:\n",
95 | "* https://www.pcg-random.org/index.html\n",
96 | "\n",
97 | "We first test it for a scalar variant of Mitsuba 3"
98 | ]
99 | },
100 | {
101 | "cell_type": "code",
102 | "execution_count": 3,
103 | "metadata": {},
104 | "outputs": [
105 | {
106 | "name": "stdout",
107 | "output_type": "stream",
108 | "text": [
109 | "\n",
110 | "The seed has been set to be 1.\n",
111 | "sampler.next_1d() = 0.9390237331390381\n",
112 | "sampler.next_1d() = 0.6919573545455933\n",
113 | "sampler.next_1d() = 0.9697715044021606\n",
114 | "\n",
115 | "The seed has been set to be 1.\n",
116 | "sampler.next_1d() = 0.9390237331390381\n",
117 | "sampler.next_1d() = 0.6919573545455933\n",
118 | "sampler.next_1d() = 0.9697715044021606\n"
119 | ]
120 | }
121 | ],
122 | "source": [
123 | "mi.set_variant('scalar_rgb')\n",
124 | "sampler = mi.load_dict({'type': 'independent'})\n",
125 | "\n",
126 | "sampler.seed(1)\n",
127 | "print(\"\\nThe seed has been set to be 1.\")\n",
128 | "print(f\"{sampler.next_1d() = }\")\n",
129 | "print(f\"{sampler.next_1d() = }\")\n",
130 | "print(f\"{sampler.next_1d() = }\")\n",
131 | "\n",
132 | "sampler.seed(1)\n",
133 | "print(\"\\nThe seed has been set to be 1.\")\n",
134 | "print(f\"{sampler.next_1d() = }\")\n",
135 | "print(f\"{sampler.next_1d() = }\")\n",
136 | "print(f\"{sampler.next_1d() = }\")"
137 | ]
138 | },
139 | {
140 | "cell_type": "markdown",
141 | "metadata": {},
142 | "source": [
143 | "For vectorized varitans in Mitsuba 3, we should specify a parameter `wavefront_size` to call `sampler.seed()`, which indicates the amount of vectorized data."
144 | ]
145 | },
146 | {
147 | "cell_type": "code",
148 | "execution_count": 4,
149 | "metadata": {},
150 | "outputs": [
151 | {
152 | "name": "stdout",
153 | "output_type": "stream",
154 | "text": [
155 | "[ERROR OCCURED!]\n",
156 | "[Sampler] Sampler::seed(): wavefront_size should be specified!\n"
157 | ]
158 | }
159 | ],
160 | "source": [
161 | "mi.set_variant('cuda_ad_rgb', 'llvm_ad_rgb')\n",
162 | "sampler = mi.load_dict({'type': 'independent'})\n",
163 | "\n",
164 | "try:\n",
165 | " sampler.seed(1)\n",
166 | "except RuntimeError as e:\n",
167 | " print(\"[ERROR OCCURED!]\")\n",
168 | " print(e)"
169 | ]
170 | },
171 | {
172 | "cell_type": "code",
173 | "execution_count": 5,
174 | "metadata": {},
175 | "outputs": [
176 | {
177 | "name": "stdout",
178 | "output_type": "stream",
179 | "text": [
180 | "\n",
181 | "The seed has been set to be 1.\n",
182 | "sampler.next_1d() = [0.0186528]\n",
183 | "sampler.next_1d() = [0.585191]\n",
184 | "sampler.next_1d() = [0.13826]\n",
185 | "\n",
186 | "The seed has been set to be 1.\n",
187 | "sampler.next_1d() = [0.0186528, 0.958362, 0.781694, 0.854458]\n",
188 | "sampler.next_1d() = [0.585191, 0.790187, 0.67082, 0.839321]\n",
189 | "sampler.next_1d() = [0.13826, 0.802202, 0.237768, 0.974834]\n",
190 | "sampler.next_2d() = [[0.294367, 0.484101],\n",
191 | " [0.664771, 0.679118],\n",
192 | " [0.657376, 0.704356],\n",
193 | " [0.0117549, 0.203928]]\n"
194 | ]
195 | }
196 | ],
197 | "source": [
198 | "sampler.seed(1, 1)\n",
199 | "print(\"\\nThe seed has been set to be 1.\")\n",
200 | "print(f\"{sampler.next_1d() = }\")\n",
201 | "print(f\"{sampler.next_1d() = }\")\n",
202 | "print(f\"{sampler.next_1d() = }\")\n",
203 | "\n",
204 | "sampler.seed(1, 4)\n",
205 | "print(\"\\nThe seed has been set to be 1.\")\n",
206 | "print(f\"{sampler.next_1d() = }\")\n",
207 | "print(f\"{sampler.next_1d() = }\")\n",
208 | "print(f\"{sampler.next_1d() = }\")\n",
209 | "print(f\"{sampler.next_2d() = }\")"
210 | ]
211 | }
212 | ],
213 | "metadata": {
214 | "kernelspec": {
215 | "display_name": "milecture360",
216 | "language": "python",
217 | "name": "python3"
218 | },
219 | "language_info": {
220 | "codemirror_mode": {
221 | "name": "ipython",
222 | "version": 3
223 | },
224 | "file_extension": ".py",
225 | "mimetype": "text/x-python",
226 | "name": "python",
227 | "nbconvert_exporter": "python",
228 | "pygments_lexer": "ipython3",
229 | "version": "3.11.10"
230 | }
231 | },
232 | "nbformat": 4,
233 | "nbformat_minor": 2
234 | }
235 |
--------------------------------------------------------------------------------
/util.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 | from os.path import join as pathjoin
3 | from time import time
4 | from typing import Tuple, Union, Sequence, Optional, Callable
5 | import numpy as np
6 | from numpy.typing import ArrayLike
7 | import matplotlib.pyplot as plt
8 |
9 | import drjit as dr
10 | import mitsuba as mi
11 |
12 | scene_dir = pathjoin("scenes", "mitsuba3_tutorial_scenes")
13 | gt_dir = output_dir = pathjoin(".", "gt_rendered")
14 | scene_labels = ["cbox", "cbox_point"]
15 | gt_spp = 40960
16 | # gt_file = "cbox_40960spp.npy"
17 |
18 | def normalize(vec: ArrayLike,
19 | axis: Optional[int] = -1
20 | ) -> ArrayLike:
21 | return vec / np.linalg.norm(vec, axis=axis, keepdims=True)
22 |
23 | def sph2vec(theta: ArrayLike, # [*]
24 | phi: ArrayLike, # [*]
25 | axis: Optional[int] = -1
26 | ) -> ArrayLike: # [*, 3] if `axis == -1`
27 | sint = np.sin(theta)
28 | x = sint*np.cos(phi)
29 | y = sint*np.sin(phi)
30 | z = np.cos(theta)
31 | return np.stack([x, y, z], axis=axis)
32 |
33 | def vec2sph(vec: ArrayLike, # *x3 (default), not assumed normalized
34 | axis: int = -1,
35 | ) -> Tuple[ArrayLike, ArrayLike]: # (theta[*], phi[*]) in radian
36 | vec = np.asarray(vec)
37 | if vec.shape[axis] != 3:
38 | raise ValueError(f"The size along given axis({axis}) should be equal to 3, but curretly shape:{vec.shape}.")
39 | vec = normalize(vec, axis=axis)
40 | vec = np.swapaxes(vec, axis, -1)
41 | X = vec[...,0]
42 | Y = vec[...,1]
43 | Z = vec[...,2]
44 | theta = np.arccos(Z)
45 | phi = np.arctan2(Y, X)
46 | return theta, phi
47 |
48 | def scale_gamma(img: ArrayLike, gm: float=2.2) -> ArrayLike:
49 | # Clip to avoid warning message from `plt.imshow`
50 | img = np.clip(img, 0.0, 1.0)
51 | return img ** (1/gm)
52 |
53 | def scale_vec2unit(vec: ArrayLike) -> ArrayLike:
54 | # Clip to avoid warning message from `plt.imshow`
55 | unit_range = vec / 2 + 0.5
56 | return np.clip(unit_range, 0.0, 1.0)
57 |
58 | def imshow_compare(img: ArrayLike,
59 | ref: ArrayLike,
60 | title_img: Optional[str] = "My answer",
61 | title_ref: Optional[str] = "GT",
62 | figsize: Optional[Tuple[float, float]] = None,
63 | suptitle: Optional[str] = None,
64 | vabs: Optional[float] = None,
65 | gm: Optional[float] = 2.2,
66 | opt_img: Optional[dict] = dict(),
67 | opt_ref: Optional[dict] = dict(),
68 | opt_diff: Optional[dict] = dict()):
69 | img = np.asarray(img)
70 | fig, axes = plt.subplots(1, 3, figsize=figsize)
71 | axes[0].imshow(scale_gamma(img, gm=gm), **opt_img)
72 | axes[0].set_title(title_img)
73 |
74 | if type(ref) == str:
75 | ref = mi.Bitmap(ref)
76 | ref = np.asarray(ref)
77 | axes[1].imshow(scale_gamma(ref, gm=gm), **opt_ref)
78 | axes[1].set_title(title_ref)
79 |
80 | diff = (img - ref).sum(-1)
81 | if vabs is None:
82 | vabs_curr = max(np.abs(diff.max()), np.abs(diff.min()))
83 | # opt_diff_curr = opt_diff
84 | else:
85 | vabs_curr = vabs
86 | # opt_diff_curr = opt_diff.copy()
87 | # opt_diff_curr.pop('vmin')
88 | # opt_diff_curr.pop('vmax')
89 | im = axes[2].imshow(diff, cmap='seismic', vmin=-vabs_curr, vmax=vabs_curr, **opt_diff)
90 |
91 | plt.colorbar(im, fraction=0.046, pad=0.04)
92 | axes[2].set_title("Diff.")
93 | if suptitle is not None:
94 | fig.suptitle(suptitle)
95 | plt.tight_layout()
96 |
97 | def imshow_compare_many(img_list: Sequence[ArrayLike],
98 | ref: Union[ArrayLike, str],
99 | title_img_list: Optional[Sequence[str]] = None,
100 | title_ref: Optional[str] = "GT",
101 | vabs: Optional[float] = None,
102 | opt_img: dict = dict(),
103 | opt_ref: dict = dict(),
104 | opt_diff: dict = dict()):
105 | n_img = len(img_list)
106 | if type(ref) == str:
107 | ref = mi.Bitmap(ref)
108 | ref = np.asarray(ref)
109 |
110 | for i, img in enumerate(img_list):
111 | img = np.asarray(img)
112 | plt.subplot(2, n_img+1, i+1)
113 | plt.imshow(scale_gamma(img), **opt_img)
114 |
115 | if title_img_list is None:
116 | title_img = f"My answer {i+1}"
117 | else:
118 | title_img = title_img_list[i]
119 | plt.title(title_img)
120 |
121 | plt.subplot(2, n_img+1, i+n_img+2)
122 | diff = (img - ref).sum(-1)
123 | if vabs is None:
124 | vabs_curr = max(np.abs(diff.max()), np.abs(diff.min()))
125 | else:
126 | vabs_curr = vabs
127 | im = plt.imshow(diff, cmap='seismic', vmin=-vabs_curr, vmax=vabs_curr, **opt_diff)
128 |
129 | plt.colorbar(im, fraction=0.046, pad=0.04)
130 | plt.title("Diff.")
131 |
132 | plt.subplot(2, n_img+1, n_img+1)
133 | plt.imshow(scale_gamma(ref), **opt_ref)
134 | plt.title(title_ref)
135 | plt.tight_layout()
136 |
137 |
138 | def test_integrator_short(integrator_name):
139 | # Options
140 | max_depth = 20
141 | spp = 1024
142 | gamma = 1/2.2
143 | print_header = "[test_integrator_short] "
144 | # print_indent = " " * len(print_header)
145 | gt_path = pathjoin(gt_dir, gt_file)
146 |
147 | # Main
148 | scene = mi.load_dict(mi.cornell_box())
149 | integrator = mi.load_dict({'type': integrator_name, 'max_depth': max_depth})
150 | t0 = time()
151 | image = mi.render(scene, integrator=integrator, spp=spp).numpy()
152 | print(f"{print_header}Rendering {spp} spp using {integrator_name} integrator done in {time()-t0:.2f} sec.")
153 |
154 | image_gt = np.load(gt_path)
155 |
156 | plt.subplot(131)
157 | plt.imshow(image ** gamma)
158 | plt.title(f"{integrator_name}, {spp} spp")
159 |
160 | plt.subplot(132)
161 | plt.imshow(image_gt ** gamma)
162 | plt.title(f"GT")
163 |
164 | plt.subplot(133)
165 | im = plt.imshow((image - image_gt).sum(-1))
166 | plt.colorbar(im, fraction=0.046, pad=0.04)
167 | plt.title(f"Rendering error")
168 |
169 | plt.show()
170 |
171 | def test_sequence(func_seq: Callable[[int], mi.Scene], scene_limit: mi.Scene,
172 | title_seq: str, title_limit: str,
173 | opt_seq=dict(), opt_limit=dict(), opt_diff=dict()):
174 | '''
175 | title_seq should contain a formatting character
176 | '''
177 | #--- Sequence configs
178 | n_list = [1, 5, 25]
179 | spp = 256
180 |
181 | #--- Main
182 | num_col = len(n_list) + 1
183 | img_limit = mi.render(scene_limit, spp=spp).numpy()
184 | plt.subplot(2, num_col, num_col)
185 | plt.imshow(scale_gamma(img_limit), **opt_limit)
186 | plt.title(title_limit)
187 |
188 | for i, n in enumerate(n_list):
189 | img_seq = mi.render(func_seq(n), spp=spp).numpy()
190 | plt.subplot(2, num_col, i+1)
191 | plt.imshow(scale_gamma(img_seq), **opt_seq)
192 | plt.title(title_seq % n)
193 |
194 | plt.subplot(2, num_col, i+1+num_col)
195 | im = plt.imshow((img_seq - img_limit).sum(-1), cmap='seismic', **opt_diff)
196 | plt.colorbar(im, fraction=0.046, pad=0.04)
197 | plt.title("Diff. from target limit")
198 |
199 | plt.show()
200 |
201 | def world2img(scene: mi.Scene, point: Union[mi.Point3f, ArrayLike]) -> mi.Point2u:
202 | '''
203 | Assume height and width are equal.
204 | '''
205 | if not isinstance(scene, mi.Scene):
206 | raise TypeError(f"Wrong type for `scene`: {type(scene)}")
207 | params = mi.traverse(scene)
208 | sensor_tf = params['sensor.to_world']
209 | size = params['sensor.film.size']
210 | w, h = size
211 | x_fov = params['sensor.x_fov']
212 | xhalf = dr.tan(dr.deg2rad(x_fov/2))
213 | yhalf = xhalf * h / w
214 |
215 | point_in_sensor = sensor_tf.inverse() @ mi.Point3f(point)
216 | point_imgplane = -mi.Point2f(point_in_sensor.x/point_in_sensor.z, point_in_sensor.y/point_in_sensor.z)
217 | pix_mi = mi.Point2u((point_imgplane / mi.Point2f(xhalf, yhalf) + 1) * size / 2) - params['sensor.film.crop_offset']
218 | return pix_mi
219 |
220 | def primary_rays(scene: mi.Scene) -> mi.Ray3f:
221 | sensor = scene.sensors()[0]
222 |
223 | # Modified from: Lines 316-000 in https://github.com/mitsuba-renderer/mitsuba3/blob/master/src/python/python/ad/integrators/common.py
224 | film = sensor.film()
225 | film_size = film.crop_size()
226 | rfilter = film.rfilter()
227 | border_size = rfilter.border_size()
228 |
229 | if film.sample_border():
230 | film_size += 2 * border_size
231 |
232 | # Compute discrete sample position
233 | idx = dr.arange(mi.UInt32, dr.prod(film_size))
234 |
235 | # Compute the position on the image plane
236 | pos = mi.Vector2i()
237 | pos.y = idx // film_size[0]
238 | pos.x = dr.fma(mi.Int32(-film_size[0]), pos.y, idx)
239 |
240 | if film.sample_border():
241 | pos -= border_size
242 |
243 | pos += mi.Vector2i(film.crop_offset())
244 |
245 | # Cast to floating point and add random offset
246 | pos_f = mi.Vector2f(pos) + 0.5
247 |
248 | # Re-scale the position to [0, 1]^2
249 | scale = dr.rcp(mi.ScalarVector2f(film.crop_size()))
250 | offset = -mi.ScalarVector2f(film.crop_offset()) * scale
251 | pos_adjusted = dr.fma(pos_f, scale, offset)
252 |
253 | aperture_sample = mi.Vector2f(0.0)
254 | ray, _ = sensor.sample_ray(
255 | time=0,
256 | sample1=0,
257 | sample2=pos_adjusted,
258 | sample3=aperture_sample
259 | )
260 | return ray
261 |
262 | # primary surface interactions
263 | def primary_sis(scene: mi.Scene) -> mi.SurfaceInteraction3f:
264 | rays = primary_rays(scene)
265 | si = scene.ray_intersect(rays)
266 | return si
267 |
268 | def primary_hits(scene: mi.Scene) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
269 | rays = primary_rays(scene)
270 | si = scene.ray_intersect(rays)
271 |
272 | pt = si.p.numpy()
273 | print(10, pt.shape)
274 | mask = si.is_valid().numpy()
275 | # prem_id = si.prim_index.numpy()
276 | shape_id = dr.full(mi.Int, -1)
277 | for i, shape in enumerate(scene.shapes()):
278 | shape_id[si.shape.eq_(mi.ShapePtr(shape))] = i
279 | shape_id = shape_id.numpy()
280 |
281 | sensor = scene.sensors()[0]
282 | film = sensor.film()
283 | w, h = film.crop_size()
284 |
285 | pt = pt.reshape(h, w, 3)
286 | mask = mask.reshape(h, w)
287 | # return pt.reshape(h, w, 3), mask.reshape(h, w), prem_id.reshape(h, w), shape_id.reshape(h, w)
288 | return pt.reshape(h, w, 3), mask.reshape(h, w), shape_id.reshape(h, w)
289 |
290 | ##################################################
291 | ### Mitsuba scene dictionary
292 | ##################################################
293 |
294 | # "Mi"tsuba objects in "dict"ionary
295 | def midict_area(rgb: ArrayLike # [3]
296 | ) -> dict:
297 | return {'type': 'area',
298 | 'radiance': {'type': 'rgb',
299 | 'value': rgb}}
300 |
301 | def midict_diffuse(reflectance: ArrayLike) -> dict:
302 | return {'type': 'diffuse',
303 | 'reflectance': {'type': 'rgb',
304 | 'value': reflectance}}
305 |
306 | def midict_sphere(center: ArrayLike,
307 | radius: float,
308 | bsdf: Union[dict, str],
309 | emitter: Union[dict, str, None] = None
310 | ) -> dict:
311 | res = {'type': 'sphere',
312 | 'center': center,
313 | 'radius': radius}
314 | if type(bsdf) == str:
315 | res['bsdf'] = {'type': 'ref', 'id': bsdf}
316 | elif type(bsdf) == dict:
317 | res['bsdf'] = bsdf
318 | else:
319 | raise TypeError(f"Unsupported type ({type(bsdf)}) for `bsdf`.")
320 | if type(emitter) == str:
321 | res['emitter'] = {'type': 'ref', 'id': emitter}
322 | elif type(emitter) == dict:
323 | res['emitter'] = emitter
324 | elif emitter is not None:
325 | raise TypeError(f"Unsupported type ({type(emitter)}) for `emitter`.")
326 | return res
327 |
328 | def scale_scene_dict(scene_dict: dict, factor: float) -> dict:
329 | T = mi.ScalarTransform4f
330 | res_dict = {}
331 | for k, v in scene_dict.items():
332 | if type(v) == dict:
333 | v_res = v.copy()
334 | else:
335 | v_res = v
336 | if k == 'sensor':
337 | tf = v['to_world']
338 | npmat = tf.matrix.numpy()
339 | # print(type(npmat))
340 | # print(npmat)
341 | # print(k)
342 | # print(k*npmat[:3, 3])
343 | npmat[:3, 3] *= factor
344 | v_res['to_world'] = mi.ScalarTransform4f(npmat)
345 | else:
346 | if 'to_world' in v:
347 | tf = v['to_world']
348 | v_res['to_world'] = T().scale([factor, factor, factor]) @ tf
349 | for mult_able in ['position', 'center', 'radius']:
350 | if mult_able in v:
351 | v_res[mult_able] = v[mult_able] * factor
352 | res_dict[k] = v_res
353 | return res_dict
354 |
355 | ##################################################
356 | ### matplotlib.pyplot
357 | ##################################################
358 |
359 | def text_at(scene: mi.Scene, point: Union[mi.Point3f, ArrayLike],
360 | text: str, ha: str="center", va: str="top"):
361 | pixpos = np.squeeze(world2img(scene, point).numpy())
362 | xoffset = 0
363 | yoffset = 10 if va == "top" else -10
364 | kargs = dict(size=10, ha=ha, va=va, bbox=dict(ec = (0.,0.,0.,0.),
365 | fc=(1.,1.,1., 0.3)))
366 | plt.text(pixpos[0] + xoffset, pixpos[1] + yoffset,
367 | text, **kargs)
368 |
369 | def show_ray(scene_dict: dict, ray: mi.Ray3f, si: mi.SurfaceInteraction3f):
370 | scene_dict['zero_bsdf'] = midict_diffuse([0, 0, 0])
371 |
372 | scene_dict['ray'] = {
373 | 'type': 'cylinder',
374 | 'p0': ray.o,
375 | 'p1': si.p,
376 | 'radius': 0.01,
377 | 'bsdf': {'type': 'ref', 'id': 'zero_bsdf'},
378 | 'emitter': midict_area([1, 0, 0])}
379 | scene_dict['origin'] = midict_sphere(ray.o, 0.03, 'zero_bsdf', midict_area([1, 1, 1]))
380 | scene_dict['intersection'] = midict_sphere(si.p, 0.03, 'zero_bsdf', midict_area([2, 0.4, 0.4]))
381 |
382 | scene_visualize = mi.load_dict(scene_dict)
383 | img = mi.render(scene_visualize, spp=100)
384 | plt.imshow(img**(1/2.2))
385 |
386 | with np.printoptions(precision=4):
387 | text_at(scene_visualize, ray.o, f"ray.o = mi.Point3f({ray.o.numpy()})")
388 | text_at(scene_visualize, si.p, f"si.p = mi.Point3f({si.p.numpy()})", ha="right", va="bottom")
389 |
390 | def show_ds(si: mi.SurfaceInteraction3f, ds_list: Sequence[mi.DirectionSample3f]):
391 | scene_dict = mi.cornell_box()
392 | scene_dict['integrator']['max_depth'] = 5
393 | scene_dict['zero_bsdf'] = midict_diffuse([0, 0, 0])
394 |
395 | # For visibility of `ds` on the light source
396 | scene_dict['light']['emitter']['radiance']['value'] = [0.2, 0.2, 0.2]
397 | scene_dict['point'] = {'type': 'point',
398 | 'position': [0, 0.2, 0.3],
399 | 'intensity': {'type': 'rgb', 'value': [0.3,0.3,0.3]}}
400 |
401 | scene_dict['si'] = midict_sphere(si.p, 0.03, 'zero_bsdf', midict_area([0.4, 0.4, 2]))
402 | for i, ds in enumerate(ds_list):
403 | scene_dict[f'ds{i}'] = midict_sphere(ds.p, 0.005, 'zero_bsdf', midict_area([2, 0.4, 0.4]))
404 | scene_visualize = mi.load_dict(scene_dict)
405 | spp = 512
406 | img = mi.render(scene_visualize, spp=spp)
407 | plt.subplot(1, 2, 1)
408 | plt.imshow(img**(1/2.2))
409 |
410 | text_at(scene_visualize, si.p, f"si.p = mi.Point3f({si.p.numpy().round(2)})", ha='right', va='top')
411 | # ds0p = ds_list[0].p
412 | # text_at(ds0p, scene_visualize, f"ds_list[0].p = mi.Point3f({ds0p})", ha="center", va="top")
413 |
414 | scene_dict['sensor']['to_world'] = mi.ScalarTransform4f().look_at([0, 0, 0], [0, 1, 0], [0, 0, 1])
415 | scene_visualize = mi.load_dict(scene_dict)
416 | img = mi.render(scene_visualize, spp=spp)
417 | plt.subplot(1, 2, 2)
418 | plt.imshow(img**(1/2.2))
--------------------------------------------------------------------------------