-
Notifications
You must be signed in to change notification settings - Fork 14
/
mesh.py
162 lines (135 loc) · 7.49 KB
/
mesh.py
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import sys
import argparse
from pathlib import Path
from typing import List, Tuple
from tqdm import tqdm
sys.path.append('')
import set_python_path
from mitsuba.core import *
from mitsuba.render import RenderJob
import util
import cameras
def prepare_ply_mesh(filepath: Path, spectrum, transformation=None):
"""
Prepare a ply mesh: Load from given filepath, apply given color spectrum and (optionally) transformation.
Uses a two-sided bsdf (so no black faces) and recomputed the normals before returning
:param filepath: Path to a ply file
:param spectrum: The color spectrum to use for the diffuse bsdf used for the mesh
:param transformation: Optional transformation to apply to the mesh (toWorld transform)
:return: A mitsuba mesh object
"""
assert filepath.suffix == '.ply', f"{filepath} does not seem to be a ply file"
mesh = PluginManager.getInstance().create({
'type': 'ply',
'filename': str(filepath),
'bsdf': {
'type': 'twosided',
'bsdf': {
'type': 'diffuse',
'diffuseReflectance': spectrum
}
},
'toWorld': transformation if transformation is not None else Transform()
})
mesh.computeNormals(True)
return mesh
def render_multiple_perspectives(mesh_path: Tuple[Path, Path], sensors: list, num_workers=8) -> None:
"""
Render one mesh from multiple camera perspectives
:param mesh_path: Path tuples (input_filepath, output_dirpath) of the mesh to render
:param sensors: The Mitsuba sensor definitions to render the mesh with
:param num_workers: Number of CPU cores to use for rendering
:return:
"""
queue = util.prepare_queue(num_workers)
input_path, output_path = mesh_path
# Make Mesh
mesh = prepare_ply_mesh(input_path, util.get_predefined_spectrum('light_blue'))
with tqdm(total=len(sensors), bar_format='Total {percentage:3.0f}% {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}] {desc}', dynamic_ncols=True) as t:
util.redirect_logger(tqdm.write, EWarn, t)
for idx, sensor in enumerate(sensors):
t.write(f"Rendering with sensor {idx}")
# Make Scene
scene = util.construct_simple_scene([mesh], sensor)
scene.setDestinationFile(str(output_path / f'{input_path.stem}-{idx}.png'))
# Make Result
job = RenderJob(f'Render-{input_path.stem}-{idx}', scene, queue)
job.start()
queue.waitLeft(0)
queue.join()
t.update()
def render_multiple_meshes(mesh_paths: List[Tuple[Path, Path]],
radius_multiplier=3., positioning_vector=Vector3(0, 1, 1), tilt=Transform.rotate(util.axis_unit_vector('x'), 20.),
width=1920, height=1440, num_samples=256, num_workers=8) -> None:
"""
Render multiple meshes with the camera always in the same relative position (based on the mesh).
:param mesh_paths: Path tuples (input_filepath, output_dirpath) of the meshes to render
:param radius_multiplier: Parameter passed to cameras.create_transform_on_bbsphere
:param positioning_vector: Parameter passed to cameras.create_transform_on_bbsphere
:param tilt: Parameter passed to cameras.create_transform_on_bbsphere
:param width: Parameter passed to cameras.create_sensor_from_transform
:param height: Parameter passed to cameras.create_sensor_from_transform
:param num_samples: Parameter passed to cameras.create_sensor_from_transform
:param num_workers: Number of CPU cores to use for rendering
:return:
"""
queue = util.prepare_queue(num_workers)
mesh_paths = list(mesh_paths)
with tqdm(total=len(mesh_paths), bar_format='Total {percentage:3.0f}% {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}] {desc}', dynamic_ncols=True) as t:
util.redirect_logger(tqdm.write, EWarn, t)
for mesh_path in mesh_paths:
input_path, output_path = mesh_path
t.write(f'Rendering {input_path.stem}')
# Make Mesh
mesh = prepare_ply_mesh(input_path, util.get_predefined_spectrum('light_blue'))
# Make sensor
sensor_transform = cameras.create_transform_on_bbsphere(mesh.getAABB(), radius_multiplier, positioning_vector, tilt)
sensor = cameras.create_sensor_from_transform(sensor_transform, width, height, fov=45., num_samples=num_samples)
# Make Scene
scene = util.construct_simple_scene([mesh], sensor)
scene.setDestinationFile(str(output_path / f'{input_path.stem}.png'))
# Make Result
job = RenderJob(f'Render-{input_path.stem}', scene, queue)
job.start()
queue.waitLeft(0)
queue.join()
t.update()
def main(args):
input_paths = [Path(path_str) for path_str in args.input_paths]
output_path = Path(args.output)
assert all([path.exists() for path in input_paths])
assert output_path.parent.is_dir()
if not output_path.is_dir():
output_path.mkdir(parents=False)
if args.scenes_list is None and args.scene is None:
mesh_paths = util.generate_mesh_paths(input_paths, output_path)
elif args.scenes_list is not None and args.scene is None:
mesh_paths = util.generate_mesh_paths(input_paths, output_path, util.read_filelist(Path(args.scenes_list)))
else: # args.scene is not None:
mesh_paths = util.generate_mesh_paths(input_paths, output_path, [args.scene])
if args.cameras is None:
render_multiple_meshes(mesh_paths,
radius_multiplier=3., positioning_vector=Vector3(0, 1, 1),
tilt=Transform.rotate(util.axis_unit_vector('x'), 20.),
width=args.width, height=args.height, num_samples=args.samples,
num_workers=args.workers)
else:
sensor_transforms = cameras.read_meshlab_sensor_transforms(Path(args.cameras))
sensors = [cameras.create_sensor_from_transform(transform, args.width, args.height, fov=45., num_samples=args.samples)
for transform in sensor_transforms]
render_multiple_perspectives(next(mesh_paths), sensors, num_workers=args.workers)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Render directory')
parser.add_argument('input_paths', nargs='*', help='Path(s) to directory containing all files to render')
parser.add_argument('-o', '--output', required=True, help='Path to write renderings to')
# Scene render parameters
parser.add_argument('--scenes_list', required=False, help='Path to file containing filenames to render in base path')
parser.add_argument('--scene', required=False, help='One scene. Overrides scenes_list')
parser.add_argument('--cameras', required=False, help='XML file containing meshlab cameras')
# Sensor parameters
parser.add_argument('--width', default=1280, type=int, help='Width of the resulting image')
parser.add_argument('--height', default=960, type=int, help='Height of the resulting image')
parser.add_argument('--samples', default=128, type=int, help='Number of integrator samples per pixel')
# General render parameters
parser.add_argument('--workers', required=False, default=8, type=int, help="How many concurrent workers to use")
main(parser.parse_args())