163 lines
7.5 KiB
Python
163 lines
7.5 KiB
Python
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())
|