├── README.md ├── custom_bsdf ├── measuredbtf.py └── utils │ ├── btf_interpolator.py │ └── coord_system_transfer.py ├── documents ├── cloth_diffuse_checker.jpg ├── cloth_fabric12.jpg ├── cloth_wool.jpg ├── dou2023.jpg ├── kavoosighafi2023.jpg ├── matpreview_corduroy.jpg ├── matpreview_impalla.jpg ├── matpreview_leather09.jpg ├── simple_sphere_impalla.jpg ├── simple_sphere_leather10.jpg ├── simple_sphere_p1.jpg ├── simple_sphere_p2.jpg ├── simple_sphere_p32.jpg └── simple_sphere_p4.jpg ├── download_large_data.py ├── rendering.py └── scenes ├── cloth ├── cloth.obj ├── cloth.xml ├── cloth_diffuse_checker.xml ├── cloth_ubo2003_gpu.xml └── cloth_ubo2014_gpu.xml ├── matpreview ├── matpreview.serialized ├── matpreview.xml ├── matpreview_ubo2003_gpu.xml └── matpreview_ubo2014_gpu.xml └── simple_sphere ├── simple_sphere.xml ├── simple_sphere_ubo2003_gpu.xml └── simple_sphere_ubo2014_gpu.xml /README.md: -------------------------------------------------------------------------------- 1 | # BTF Rendering 2 | Custom plugin in Python to render measured BTF (Bidirectional Texture Function) with Mitsuba 2. 3 | 4 | ![](documents/cloth_wool.jpg) 5 | 6 | ## Requirement in Python 7 | Make sure that the following libraries are available in python. 8 | - [SciPy](https://www.scipy.org/) 9 | - [BTF Extractor](https://github.com/2-propanol/BTF_extractor) 10 | - [tqdm](https://github.com/tqdm/tqdm) (optional) 11 | 12 | ## Usage 13 | 1. Clone and compile [Mitsuba 2](https://github.com/mitsuba-renderer/mitsuba2). Check whether you can `import mitsuba` in python. 14 | 2. Clone this repository and move it there. 15 | ```bash 16 | git clone https://github.com/elerac/btf-rendering.git 17 | cd btf-rendering 18 | ``` 19 | 3. Download [UBO2003 BTF Dataset](https://cg.cs.uni-bonn.de/en/projects/btfdbb/download/ubo2003/). Make directory named `UBO2003/` and save the data under there. OR, you can run [download_large_data.py](https://github.com/elerac/btf-rendering/blob/master/download_large_data.py) and the data will download automatically. 20 | Have you placed BTF Dataset as shown below? 21 | ``` 22 | . 23 | ├── UBO2003 24 | │   ├── UBO_IMPALLA256.zip 25 | │   ├── ... 26 | ├── custom_bsdf 27 | ├── download_large_data.py 28 | ├── rendering.py 29 | ├── scenes 30 | ├── ... 31 | ``` 32 | 4. Run [rendering.py](https://github.com/elerac/btf-rendering/blob/master/rendering.py) and get [rendered image using BTF](https://github.com/elerac/btf-rendering/blob/master/documents/simple_sphere_impalla.jpg). 33 | 34 | ## Measured BTF (*measuredbtf*) 35 | | Parameter | Type | Description | 36 | | :-- | :-- | :-- | 37 | | filename | string | Filename of the BTF database file to be loaded. The type of dataset is distinguished by its extension. .zip for UBO2003, .btf for UBO2014.| 38 | | reflectance| float | Adjust the reflectance of the BTF. (Default: 1.0) | 39 | | apply_inv_gamma | boolean | Whether to apply inverse gamma correction. If the input is the gamma-corrected image, this process should be applied. (Default: *true*) | 40 | | power_parameter | float | Determine the smoothness of the interpolation. The smaller the value, the smoother it is. (Default: 4.0) | 41 | | wrap_mode | string | Controls the behavior of texture evaluations that fall outside of the [0,1] range. The `repeat` and `mirror` options are currently available. (Default: `repeat`)| 42 | | to_uv | transform | Specifies an optional 3x3 UV transformation matrix. A 4x4 matrix can also be provided, in which case the extra row and column are ignored. (Default: none) | 43 | 44 | | | | 45 | | :-: | :-: | 46 | | ![](documents/matpreview_impalla.jpg)| ![](documents/matpreview_leather09.jpg) | 47 | | UBO2003 IMPALLA | UBO2014 leather09 | 48 | 49 | This custom plugin implements a BTF for rendering reflections of textures taken in a real scene. The BTF is a set of images with different illumination and viewing directions. 50 | 51 | `filename` is the name of the BTF database file. This file should follow the format of the BTF dataset of University of Bonn. 52 | Download the [UBO2003](https://cg.cs.uni-bonn.de/en/projects/btfdbb/download/ubo2003/) or [ATRIUM](https://cg.cs.uni-bonn.de/en/projects/btfdbb/download/atrium/) or [UBO2014](https://cg.cs.uni-bonn.de/en/projects/btfdbb/download/ubo2014/) dataset for rendering. 53 | 54 | ```xml 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ``` 63 | 64 | ### Interpolation and Power Parameter 65 | This custom plugin interpolates BTF. The interpolation is done by [k-nearest neighbor sampling](https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm) and [inverse distance weighting](https://en.wikipedia.org/wiki/Inverse_distance_weighting). 66 | The weight of the inverse distance weighting can be adjusted by the `power_parameter` *p*. The parameter *p* determines the influence of the distance between the interpolation points. The smaller *p* is, the smoother the interpolation will be. 67 | 68 | The following figures show the difference in appearance when *p* is changed. When *p* is small (*p*=1), the texture is smooth and specular reflection is weak. On the other hand, when *p* is large (*p*=32), the texture has discontinuous boundaries. This is because the angle of the captured BTF data is sparse. 69 | | | | | | 70 | | :-: | :-: | :-: | :-: | 71 | | ![](documents/simple_sphere_p1.jpg) | ![](documents/simple_sphere_p2.jpg) | ![](documents/simple_sphere_p4.jpg) | ![](documents/simple_sphere_p32.jpg) | 72 | | *p* = 1 | *p* = 2 | *p* = 4 | *p* = 32 | 73 | 74 | 75 | ## Warning 76 | In the `scalar_rgb` variant, the execution of this plugin is **extremely slow**. Thus, using the `gpu_rgb` variant is recommended. 77 | 78 | ## Rendered Images from Other Papers 79 | 80 | I collected the rendered images from other papers that employ the 3D model of cloth originated at this repository ([`scenes/cloth/cloth.obj`](scenes/cloth/)). These beautifully rendered images enhance the effectiveness of their excellent methods for BTF data processing. 81 | 82 | ![Rendered image borrowed from Fig.1](documents/kavoosighafi2023.jpg) 83 | *Kavoosighafi et al., "SparseBTF: Sparse Representation Learning for Bidirectional Texture Functions", Eurographics Symposium on Rendering, 2023. [[Link]](https://diglib.eg.org/handle/10.2312/sr20231123)* 84 | 85 | ![Rendered image borrowed from Fig.10](documents/dou2023.jpg) 86 | *Dou et al., "Real-Time Neural BRDF with Spherically Distributed Primitives", arXiv, 2023. [[Link]](https://arxiv.org/abs/2310.08332)* 87 | 88 | You are free to use my 3D model and scene file of cloth (and also my code) for academic purposes. If you use them, it is not necessary, but I would appreciate it if you refer to my name. 89 | -------------------------------------------------------------------------------- /custom_bsdf/measuredbtf.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .utils.btf_interpolator import BtfInterpolator 3 | from .utils.coord_system_transfer import orthogonal2spherical, mirror_uv 4 | 5 | import enoki as ek 6 | from mitsuba.core import Bitmap, Struct, Thread, math, Properties, Frame3f, Float, Vector3f, warp, Transform3f 7 | from mitsuba.render import BSDF, BSDFContext, BSDFFlags, BSDFSample3f, SurfaceInteraction3f, register_bsdf, Texture 8 | 9 | class MeasuredBTF(BSDF): 10 | def __init__(self, props): 11 | BSDF.__init__(self, props) 12 | 13 | # BTDFFのzipファイルのパス 14 | self.m_filename = props["filename"] 15 | 16 | # 逆ガンマ補正をかけるかどうか 17 | if props.has_property("apply_inv_gamma"): 18 | self.m_apply_inv_gamma = props["apply_inv_gamma"] 19 | else: 20 | self.m_apply_inv_gamma = True 21 | 22 | # 反射率 23 | if props.has_property("reflectance"): 24 | self.m_reflectance = Float(props["reflectance"]) 25 | else: 26 | self.m_reflectance = Float(1.0) 27 | 28 | # Power parameter 29 | if props.has_property("power_parameter"): 30 | self.m_power_parameter = Float(props["power_parameter"]) 31 | else: 32 | self.m_power_parameter = Float(4.0) 33 | 34 | # Wrap mode 35 | if props.has_property("wrap_mode"): 36 | self.m_wrap_mode = str(props["wrap_mode"]) 37 | else: 38 | self.m_wrap_mode = "repeat" 39 | 40 | # UVマップの変換 41 | self.m_transform = Transform3f(props["to_uv"].extract()) 42 | 43 | # 読み込んだBTF 44 | self.btf = BtfInterpolator(self.m_filename, p=self.m_power_parameter) 45 | 46 | self.m_flags = BSDFFlags.DiffuseReflection | BSDFFlags.FrontSide 47 | self.m_components = [self.m_flags] 48 | 49 | def get_btf(self, wi, wo, uv): 50 | """ 51 | BTFの生の値を取得する 52 | 53 | wi : enoki.scalar.Vector3f 54 | Incident direction 55 | 56 | wo : enoki.scalar.Vector3f 57 | Outgoing direction 58 | 59 | uv : enoki.scalar.Vector2f 60 | UV surface coordinates 61 | """ 62 | # カメラ側の方向 63 | _, tv, pv = orthogonal2spherical(*wi) 64 | 65 | # 光源側の方向 66 | _, tl, pl = orthogonal2spherical(*wo) 67 | 68 | # 画像中の座標位置を求める 69 | uv = self.m_transform.transform_point(uv) 70 | 71 | # warp_modeがmirrorなら座標の変換 72 | if self.m_wrap_mode == "mirror": 73 | uv = mirror_uv(uv).T 74 | 75 | u, v = uv 76 | 77 | # BTFの画素値を取得 78 | bgr = self.btf.angles_uv_to_pixel(tl, pl, tv, pv, u, v) 79 | 80 | # 0.0~1.0にスケーリング 81 | bgr = np.clip(bgr / 255.0 * self.m_reflectance, 0, 1) 82 | 83 | # 逆ガンマ補正をかける 84 | if self.m_apply_inv_gamma: 85 | bgr **= 2.2 86 | 87 | return Vector3f(bgr[..., ::-1]) 88 | 89 | def sample(self, ctx, si, sample1, sample2, active): 90 | """ 91 | BSDF sampling 92 | """ 93 | cos_theta_i = Frame3f.cos_theta(si.wi) 94 | 95 | active &= cos_theta_i > 0 96 | 97 | bs = BSDFSample3f() 98 | bs.wo = warp.square_to_cosine_hemisphere(sample2) 99 | bs.pdf = warp.square_to_cosine_hemisphere_pdf(bs.wo) 100 | bs.eta = 1.0 101 | bs.sampled_type = +BSDFFlags.DiffuseReflection 102 | bs.sampled_component = 0 103 | 104 | value = self.get_btf(si.wi, bs.wo, si.uv) / Frame3f.cos_theta(bs.wo) 105 | 106 | return ( bs, ek.select(active & (bs.pdf > 0.0), value, Vector3f(0)) ) 107 | 108 | def eval(self, ctx, si, wo, active): 109 | """ 110 | Emitter sampling 111 | """ 112 | if not ctx.is_enabled(BSDFFlags.DiffuseReflection): 113 | return Vector3f(0) 114 | 115 | cos_theta_i = Frame3f.cos_theta(si.wi) 116 | cos_theta_o = Frame3f.cos_theta(wo) 117 | 118 | value = self.get_btf(si.wi, wo, si.uv) * math.InvPi 119 | 120 | return ek.select((cos_theta_i > 0.0) & (cos_theta_o > 0.0), value, Vector3f(0)) 121 | 122 | def pdf(self, ctx, si, wo, active): 123 | if not ctx.is_enabled(BSDFFlags.DiffuseReflection): 124 | return Vector3f(0) 125 | 126 | cos_theta_i = Frame3f.cos_theta(si.wi) 127 | cos_theta_o = Frame3f.cos_theta(wo) 128 | 129 | pdf = warp.square_to_cosine_hemisphere_pdf(wo) 130 | 131 | return ek.select((cos_theta_i > 0.0) & (cos_theta_o > 0.0), pdf, 0.0) 132 | -------------------------------------------------------------------------------- /custom_bsdf/utils/btf_interpolator.py: -------------------------------------------------------------------------------- 1 | """ 2 | BTFDBBを元に.任意角度のBTF画像を補間して返すためのライブラリ. 3 | 任意の角度(tl, pl, tv, pv)から,補間したndarray形式の画像を取得. 4 | 5 | BTFDBBの読み込みについては,btf-extractor(https://github.com/2-propanol/BTF_extractor)を参照. 6 | """ 7 | import os 8 | import datetime 9 | import numpy as np 10 | from scipy.spatial import cKDTree 11 | from btf_extractor import Ubo2003, Ubo2014 12 | from .coord_system_transfer import spherical2orthogonal 13 | 14 | # import tqdm if available 15 | import importlib 16 | tqdm_spec = importlib.util.find_spec("tqdm") 17 | use_tqdm = tqdm_spec is not None 18 | if use_tqdm: 19 | from tqdm import tqdm 20 | 21 | class BtfInterpolator: 22 | """ 23 | BTFDBBを元に,任意角度のBTF画像を補間して返す. 24 | 補間は,k近傍のデータから,逆距離加重法(Inverse Distance Weighting)を用いている. 25 | """ 26 | def __init__(self, filepath, k=4, p=4.0): 27 | """ 28 | BTFDBBを読み込み,補間器を生成する. 29 | (読み込みには少し時間がかかります) 30 | 31 | Parameters 32 | ---------- 33 | filepath : str 34 | BTFDBBのファイルのパス 35 | 拡張子が".zip"の場合は"Ubo2003" 36 | 拡張子が".btf"の場合は"Ubo2014" 37 | k : int 38 | 補間に用いる近傍点の数 39 | p : float 40 | 逆距離加重補間の近傍点の影響の強さを決定する 41 | pは小さいほど近傍点の値の影響が大きくなる 42 | 43 | Methods 44 | ------- 45 | angles_xy_to_pixel(tl, pl, tv, pv, x, y) 46 | `tl`, `pl`, `tv`, `pv`の角度条件で`x`,`y`の座標の画像値を補間して返す 47 | angles_uv_to_pixel(tl, pl, tv, pv, u, v) 48 | `tl`, `pl`, `tv`, `pv`の角度条件で`u`,`v`の座標の画像値を補間して返す 49 | angles_to_image(tl, pl, tv, pv) 50 | `tl`, `pl`, `tv`, `pv`の角度条件の画像値を補間して返す 51 | 52 | Notes 53 | ----- 54 | 光源側の角度は`tl`,`pl` 55 | 観測側の角度は`tv`,`pv` 56 | """ 57 | self.k = k 58 | self.p = p 59 | 60 | # BTFDBBの読み込み 61 | root, ext = os.path.splitext(filepath) 62 | if ext==".zip": 63 | btf = Ubo2003(filepath) 64 | elif ext==".btf": 65 | btf = Ubo2014(filepath) 66 | elif ext==".npz": 67 | # https://github.com/2-propanol/btfnpz_helper 68 | from btfnpz import Btfnpz 69 | btf = Btfnpz(filepath) 70 | else: 71 | raise Exception("The filepath must have a .zip or .btf extension.") 72 | 73 | # 画像のサイズを取得 74 | angles_list = list(btf.angles_set) 75 | num = len(angles_list) 76 | img_dummy = btf.angles_to_image(*angles_list[0]) 77 | height, width, channel = img_dummy.shape 78 | dtype = img_dummy.dtype 79 | self.__num = num 80 | self.__height = height 81 | self.__width = width 82 | 83 | # すべてのファイルの実態と角度情報を読み込みリストを生成 84 | self.__images = np.empty((num, height, width, channel), dtype=dtype) 85 | points = np.empty((num, 6), dtype=np.float32) 86 | 87 | time_now = (datetime.datetime.now()).strftime('%Y-%m-%d %H:%M:%S') 88 | print(f"{time_now} INFO [MeasuredBTF] Loading BTF dataset \"{filepath}\" ..") 89 | if use_tqdm: 90 | angles_iterable = tqdm(angles_list) 91 | else: 92 | angles_iterable = angles_list 93 | 94 | for i, (tl, pl, tv, pv) in enumerate(angles_iterable): 95 | # 画像と角度を読み込み 96 | img_bgr = btf.angles_to_image(tl, pl, tv, pv) 97 | 98 | # 角度を球面座標から直交座標へ変換 99 | xl, yl, zl = spherical2orthogonal(1.0, tl, pl) 100 | xv, yv, zv = spherical2orthogonal(1.0, tv, pv) 101 | point = np.array([xl, yl, zl, xv, yv, zv], dtype=np.float32) 102 | 103 | # 画像と角度をリストに保存 104 | self.__images[i] = img_bgr 105 | points[i] = point 106 | 107 | # 角度の情報からKDTreeを構築 108 | self.__kd_tree = cKDTree(points) 109 | 110 | def __uv_to_xy(self, u, v): 111 | """uv座標(float)を,BTF画像に対応するxy座標(int)に変換する 112 | """ 113 | xf = np.mod(u * (self.__width-1), self.__width) 114 | yf = np.mod(v * (self.__height-1), self.__height) 115 | x = np.array(xf, dtype=np.uint16) 116 | y = np.array(yf, dtype=np.uint16) 117 | return x, y 118 | 119 | def angles_xy_to_pixel(self, tl, pl, tv, pv, x, y): 120 | """ 121 | `tl`, `pl`, `tv`, `pv`の角度条件で`x`,`y`の座標の画像値を補間して返す 122 | 123 | Parameters 124 | ---------- 125 | tl, pl : array of floats 126 | 光源の方向(tl:theta, pl:phi) 127 | tv, pv : array of floats 128 | カメラの方向(tv:theta, pv:phi) 129 | x, y : array of floatss 130 | BTFのxy画像座標 131 | 132 | Returns 133 | ------- 134 | pixel : array of floats 135 | BTFの画素値 136 | """ 137 | # 角度は球面座標から直交座標へ変換する. 138 | xl, yl, zl = spherical2orthogonal(1.0, tl, pl) 139 | xv, yv, zv = spherical2orthogonal(1.0, tv, pv) 140 | point = np.array([xl, yl, zl, xv, yv, zv], dtype=np.float32).T 141 | 142 | # k近傍探索を実行 143 | # 距離はl2ノルム 144 | distance, index = self.__kd_tree.query(point, k=self.k, p=2, workers=-1) 145 | 146 | # 対応する角度・xy座標のBTF画像の値を取得 147 | index = np.clip(index, 0, self.__num-1) 148 | x = np.expand_dims(np.clip(x, 0, self.__width-1), axis=-1) 149 | y = np.expand_dims(np.clip(y, 0, self.__height-1), axis=-1) 150 | btf_values = self.__images[index, y, x] 151 | 152 | if self.k==1: 153 | # 補間なし 154 | pixel = btf_values 155 | else: 156 | # 逆距離加重法による補間 157 | weights = np.expand_dims( 1/(distance+10**-32)**self.p, axis=-1) 158 | pixel = np.sum(btf_values * weights, axis=-2) / np.sum(weights, axis=-2) 159 | 160 | return pixel 161 | 162 | def angles_uv_to_pixel(self, tl, pl, tv, pv, u, v): 163 | """ 164 | `tl`, `pl`, `tv`, `pv`の角度条件で`u`,`v`の座標の画像値を補間して返す. 165 | 166 | Parameters 167 | ---------- 168 | tl, pl : array of floats 169 | 光源の方向(tl:theta, pl:phi) 170 | tv, pv : array of floats 171 | カメラの方向(tv:theta, pv:phi) 172 | u, v : array of floatss 173 | BTFのuv画像座標 174 | 175 | Returns 176 | ------- 177 | pixel : array of floats 178 | BTFの画素値 179 | """ 180 | x, y = self.__uv_to_xy(u, v) 181 | return self.angles_xy_to_pixel(tl, pl, tv, pv, x, y) 182 | 183 | def angles_to_image(self, tl, pl, tv, pv): 184 | """ 185 | `tl`, `pl`, `tv`, `pv`の角度条件の画像を補間して返す. 186 | 187 | Parameters 188 | ---------- 189 | tl, pl : array of floats 190 | 光源の方向(tl:theta, pl:phi) 191 | tv, pv : array of floats 192 | カメラの方向(tv:theta, pv:phi) 193 | 194 | Returns 195 | ------- 196 | image : array of floats 197 | BTFの画像 198 | """ 199 | x = np.arange(self.__width) 200 | y = np.arange(self.__height) 201 | return self.angles_xy_to_pixel(tl, pl, tv, pv, x, y) 202 | -------------------------------------------------------------------------------- /custom_bsdf/utils/coord_system_transfer.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def spherical2orthogonal(r, theta, phi): 4 | """ 5 | 球面座標から直交座標へ変換する. 6 | thetaとphiの単位は度. 7 | """ 8 | theta = np.deg2rad(theta) 9 | phi = np.deg2rad(phi) 10 | x = r * np.sin(theta) * np.cos(phi) 11 | y = r * np.sin(theta) * np.sin(phi) 12 | z = r * np.cos(theta) 13 | return x, y, z 14 | 15 | def orthogonal2spherical(x, y, z): 16 | """ 17 | 直交座標から球面座標へ変換する. 18 | thetaとphiの単位は度. 19 | """ 20 | r = np.sqrt(x*x+y*y+z*z) 21 | theta = np.arccos(z/r) 22 | x_for_arccos = x / (np.sqrt(x*x+y*y) + 10**-32) 23 | phi = np.sign(y) * np.arccos( np.clip(x_for_arccos, -1, 1) ) 24 | return r, np.rad2deg(theta), np.rad2deg(phi) 25 | 26 | def mirror_uv(uv): 27 | """ 28 | UV座標の境界が反転するように変換 29 | 30 | Parameters 31 | ---------- 32 | uv : np.ndarray 33 | 変換前のUV座標 34 | 35 | Returns 36 | ------- 37 | uv_mirror : np.ndarray 38 | 変換後のUV座標 39 | 40 | Notes 41 | ----- 42 | 変換前 -> 変換後 43 | 0.0 -> 0.0 44 | 0.1 -> 0.1 45 | 0.9 -> 0.9 46 | 1.1 -> 1.9 47 | 1.9 -> 1.1 48 | 2.1 -> 2.1 49 | 2.9 -> 2.9 50 | 3.1 -> 3.9 51 | 3.9 -> 3.1 52 | 4.1 -> 4.1 53 | """ 54 | uv_int = np.floor(uv) 55 | mask_to_mirror = np.mod(uv_int, 2) 56 | uv_mirror = np.where(mask_to_mirror, 1 - uv + 2*uv_int, uv) 57 | return uv_mirror 58 | -------------------------------------------------------------------------------- /documents/cloth_diffuse_checker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/cloth_diffuse_checker.jpg -------------------------------------------------------------------------------- /documents/cloth_fabric12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/cloth_fabric12.jpg -------------------------------------------------------------------------------- /documents/cloth_wool.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/cloth_wool.jpg -------------------------------------------------------------------------------- /documents/dou2023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/dou2023.jpg -------------------------------------------------------------------------------- /documents/kavoosighafi2023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/kavoosighafi2023.jpg -------------------------------------------------------------------------------- /documents/matpreview_corduroy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/matpreview_corduroy.jpg -------------------------------------------------------------------------------- /documents/matpreview_impalla.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/matpreview_impalla.jpg -------------------------------------------------------------------------------- /documents/matpreview_leather09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/matpreview_leather09.jpg -------------------------------------------------------------------------------- /documents/simple_sphere_impalla.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/simple_sphere_impalla.jpg -------------------------------------------------------------------------------- /documents/simple_sphere_leather10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/simple_sphere_leather10.jpg -------------------------------------------------------------------------------- /documents/simple_sphere_p1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/simple_sphere_p1.jpg -------------------------------------------------------------------------------- /documents/simple_sphere_p2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/simple_sphere_p2.jpg -------------------------------------------------------------------------------- /documents/simple_sphere_p32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/simple_sphere_p32.jpg -------------------------------------------------------------------------------- /documents/simple_sphere_p4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/documents/simple_sphere_p4.jpg -------------------------------------------------------------------------------- /download_large_data.py: -------------------------------------------------------------------------------- 1 | import urllib.error 2 | import urllib.request 3 | import os 4 | import time 5 | import ssl 6 | ssl._create_default_https_context = ssl._create_unverified_context 7 | 8 | def download_file(url, dst_dir): 9 | dst_path = os.path.join(dst_dir, os.path.basename(url)) 10 | headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"} 11 | request = urllib.request.Request(url, headers=headers) 12 | try: 13 | with urllib.request.urlopen(request) as web_file, open(dst_path, 'wb') as local_file: 14 | local_file.write(web_file.read()) 15 | except urllib.error.URLError as e: 16 | print(e) 17 | 18 | def main(): 19 | print("UBO2003 BTF Dataset") 20 | ubo2003_dir = "UBO2003" 21 | os.makedirs(ubo2003_dir, exist_ok=True) 22 | 23 | file_path = f"{ubo2003_dir}/UBO_IMPALLA256.zip" 24 | if os.path.exists(file_path): 25 | print(f" {file_path} is already exists") 26 | else: 27 | print(f" Download {file_path}...") 28 | download_file("http://cg.cs.uni-bonn.de/fileadmin/btf/UBO2003/IMPALLA/ONEFILE/UBO_IMPALLA256.zip", ubo2003_dir) 29 | time.sleep(1) 30 | 31 | file_path = f"{ubo2003_dir}/UBO_WOOL256.zip" 32 | if os.path.exists(file_path): 33 | print(f" {file_path} is already exists") 34 | else: 35 | print(f" Download {file_path}...") 36 | download_file("http://cg.cs.uni-bonn.de/fileadmin/btf/UBO2003/WOOL/ONEFILE/UBO_WOOL256.zip", ubo2003_dir) 37 | time.sleep(1) 38 | 39 | 40 | print("Envmap from hdrihaven.com") 41 | cloth_dir = "scenes/cloth" 42 | os.makedirs(cloth_dir, exist_ok=True) 43 | 44 | file_path = f"{cloth_dir}/skylit_garage_1k.hdr" 45 | if os.path.exists(file_path): 46 | print(f" {file_path} is already exists") 47 | else: 48 | print(f" Download {file_path}...") 49 | download_file("https://hdrihaven.com/files/hdris/skylit_garage_1k.hdr", cloth_dir) 50 | time.sleep(1) 51 | 52 | 53 | if __name__=="__main__": 54 | main() 55 | -------------------------------------------------------------------------------- /rendering.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | parser = argparse.ArgumentParser() 4 | parser.add_argument("-i", "--input", type=str, default="scenes/simple_sphere/simple_sphere.xml", help="Input scene filepath (.xml)") 5 | parser.add_argument("-o", "--output", type=str, default="rendered.jpg", help="Output image filepath (.jpg, .png)") 6 | parser.add_argument("-m", "--mode", type=str, default="scalar_rgb", help="Rendering mode (scalar_rgb or gpu_rgb)") 7 | args = parser.parse_args() 8 | 9 | import mitsuba 10 | mitsuba.set_variant(args.mode) 11 | from mitsuba.core import Bitmap, Struct, Thread 12 | from mitsuba.core.xml import load_file 13 | from mitsuba.render import register_bsdf 14 | 15 | from custom_bsdf.measuredbtf import MeasuredBTF 16 | 17 | def main(): 18 | # Register MeasuredBTF 19 | register_bsdf('measuredbtf', lambda props: MeasuredBTF(props)) 20 | 21 | # Filename 22 | filename_src = args.input 23 | filename_dst = args.output 24 | 25 | # Load an XML file 26 | Thread.thread().file_resolver().append(os.path.dirname(filename_src)) 27 | scene = load_file(filename_src) 28 | 29 | # Rendering 30 | scene.integrator().render(scene, scene.sensors()[0]) 31 | 32 | # Save image 33 | film = scene.sensors()[0].film() 34 | bmp = film.bitmap(raw=True) 35 | bmp.convert(Bitmap.PixelFormat.RGB, Struct.Type.UInt8, srgb_gamma=True).write(filename_dst) 36 | 37 | if __name__=="__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /scenes/cloth/cloth.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 | -------------------------------------------------------------------------------- /scenes/cloth/cloth_diffuse_checker.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 | -------------------------------------------------------------------------------- /scenes/cloth/cloth_ubo2003_gpu.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 | -------------------------------------------------------------------------------- /scenes/cloth/cloth_ubo2014_gpu.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 | -------------------------------------------------------------------------------- /scenes/matpreview/matpreview.serialized: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elerac/btf-rendering/c7209b865b1bfe54ee0b6df6d3c3f06e46a7bcad/scenes/matpreview/matpreview.serialized -------------------------------------------------------------------------------- /scenes/matpreview/matpreview.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 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 | -------------------------------------------------------------------------------- /scenes/matpreview/matpreview_ubo2003_gpu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 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 | -------------------------------------------------------------------------------- /scenes/matpreview/matpreview_ubo2014_gpu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 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 | -------------------------------------------------------------------------------- /scenes/simple_sphere/simple_sphere.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 | -------------------------------------------------------------------------------- /scenes/simple_sphere/simple_sphere_ubo2003_gpu.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 | -------------------------------------------------------------------------------- /scenes/simple_sphere/simple_sphere_ubo2014_gpu.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 | --------------------------------------------------------------------------------