From f4710c44bb8e1aa8fc836165caff6e129e297965 Mon Sep 17 00:00:00 2001 From: Alexander Raistrick Date: Sun, 13 Oct 2024 16:29:20 -0400 Subject: [PATCH] Make split_inview accept query multiple cameras, refactor camera handling --- infinigen/assets/objects/rocks/boulder.py | 8 +- infinigen/assets/objects/trees/generate.py | 8 +- infinigen/core/execute_tasks.py | 30 ++-- infinigen/core/placement/camera.py | 137 +++++++--------- infinigen/core/placement/placement.py | 49 +++--- infinigen/core/placement/split_in_view.py | 151 +++++++++++++----- infinigen/core/rendering/render.py | 17 +- infinigen/core/util/camera.py | 47 +----- .../configs_nature/multiview_stereo.gin | 12 +- infinigen_examples/generate_asset_demo.py | 4 +- infinigen_examples/generate_indoors.py | 6 +- infinigen_examples/generate_nature.py | 60 ++++--- .../util/generate_indoors_util.py | 5 +- 13 files changed, 269 insertions(+), 265 deletions(-) diff --git a/infinigen/assets/objects/rocks/boulder.py b/infinigen/assets/objects/rocks/boulder.py index 02bbd2658..d67233e50 100644 --- a/infinigen/assets/objects/rocks/boulder.py +++ b/infinigen/assets/objects/rocks/boulder.py @@ -35,7 +35,7 @@ class BoulderFactory(AssetFactory): def __init__( self, factory_seed, - meshing_camera=None, + meshing_cameras=None, adapt_mesh_method="remesh", cam_meshing_max_dist=1e7, coarse=False, @@ -43,7 +43,7 @@ def __init__( ): super(BoulderFactory, self).__init__(factory_seed, coarse) - self.camera = meshing_camera + self.cameras = meshing_cameras self.cam_meshing_max_dist = cam_meshing_max_dist self.adapt_mesh_method = adapt_mesh_method @@ -166,10 +166,10 @@ def geo_extrusion(nw: NodeWrangler, extrude_scale=1): nw.new_node(Nodes.GroupOutput, input_kwargs={"Geometry": geometry}) def create_asset(self, i, placeholder, face_size=0.01, distance=0, **params): - if self.camera is not None and distance < self.cam_meshing_max_dist: + if self.cameras is not None and distance < self.cam_meshing_max_dist: assert self.adapt_mesh_method != "remesh" skin_obj, outofview, vert_dists, _ = split_inview( - placeholder, cam=self.camera, vis_margin=0.15 + placeholder, cameras=self.cameras, vis_margin=0.15 ) butil.parent_to(outofview, skin_obj, no_inverse=True, no_transform=True) face_size = detail.target_face_size(vert_dists.min()) diff --git a/infinigen/assets/objects/trees/generate.py b/infinigen/assets/objects/trees/generate.py index 32ca44f7a..0019ac90d 100644 --- a/infinigen/assets/objects/trees/generate.py +++ b/infinigen/assets/objects/trees/generate.py @@ -58,7 +58,7 @@ def __init__( child_col, trunk_surface, realize=False, - meshing_camera=None, + meshing_cameras=None, cam_meshing_max_dist=1e7, coarse_mesh_placeholder=False, adapt_mesh_method="remesh", @@ -73,7 +73,7 @@ def __init__( self.trunk_surface = trunk_surface self.realize = realize - self.camera = meshing_camera + self.cameras = meshing_cameras self.cam_meshing_max_dist = cam_meshing_max_dist self.adapt_mesh_method = adapt_mesh_method self.decimate_placeholder_levels = decimate_placeholder_levels @@ -168,12 +168,12 @@ def create_asset( ), ) - if self.camera is not None and distance < self.cam_meshing_max_dist: + if self.cameras is not None and distance < self.cam_meshing_max_dist: assert self.adapt_mesh_method != "remesh" skin_obj_cleanup = skin_obj skin_obj, outofview, vert_dists, _ = split_inview( - skin_obj, cam=self.camera, vis_margin=0.15 + skin_obj, cameras=self.cameras, vis_margin=0.15 ) butil.parent_to(outofview, skin_obj, no_inverse=True, no_transform=True) diff --git a/infinigen/core/execute_tasks.py b/infinigen/core/execute_tasks.py index e741d017e..1436b5b13 100644 --- a/infinigen/core/execute_tasks.py +++ b/infinigen/core/execute_tasks.py @@ -47,7 +47,7 @@ def get_scene_tag(name): def render( scene_seed, output_folder, - camera_id, + camera, render_image_func=render_image, resample_idx=None, hide_water=False, @@ -59,7 +59,7 @@ def render( if resample_idx is not None and resample_idx != 0: resample_scene(int_hash((scene_seed, resample_idx))) with Timer("Render Frames"): - render_image_func(frames_folder=Path(output_folder), camera_id=camera_id) + render_image_func(frames_folder=Path(output_folder), camera=camera) def is_static(obj): @@ -87,8 +87,9 @@ def is_static(obj): @gin.configurable def save_meshes( - scene_seed, - output_folder, + scene_seed: int, + output_folder: Path, + cameras: list[bpy.types.Object], frame_range, resample_idx=False, point_trajectory_src_frame=1, @@ -108,8 +109,9 @@ def save_meshes( for obj in bpy.data.objects: obj.hide_viewport = not (not obj.hide_render and is_static(obj)) frame_idx = point_trajectory_src_frame - frame_info_folder = Path(output_folder) / f"frame_{frame_idx:04d}" + frame_info_folder = output_folder / f"frame_{frame_idx:04d}" frame_info_folder.mkdir(parents=True, exist_ok=True) + logger.info("Working on static objects") exporting.save_obj_and_instances( frame_info_folder / "static_mesh", @@ -128,9 +130,9 @@ def save_meshes( ): bpy.context.scene.frame_set(frame_idx) bpy.context.view_layer.update() - frame_info_folder = Path(output_folder) / f"frame_{frame_idx:04d}" + frame_info_folder = output_folder / f"frame_{frame_idx:04d}" frame_info_folder.mkdir(parents=True, exist_ok=True) - logger.info(f"Working on frame {frame_idx}") + logger.info(f"save_meshes processing {frame_idx=}") exporting.save_obj_and_instances( frame_info_folder / "mesh", @@ -138,7 +140,7 @@ def save_meshes( current_frame_mesh_id_mapping, ) cam_util.save_camera_parameters( - camera_ids=cam_util.get_cameras_ids(), + camera_ids=cameras, output_folder=frame_info_folder / "cameras", frame=frame_idx, ) @@ -246,12 +248,15 @@ def execute_tasks( with open(outpath / "info.pickle", "wb") as f: pickle.dump(info, f, protocol=pickle.HIGHEST_PROTOCOL) - cam_util.set_active_camera(*camera_id) + camera_rigs = cam_util.get_camera_rigs() + camrig_id, subcam_id = camera_id + active_camera = camera_rigs[camrig_id].children[subcam_id] + cam_util.set_active_camera(active_camera) group_collections() if Task.Populate in task and populate_scene_func is not None: - populate_scene_func(output_folder, scene_seed) + populate_scene_func(output_folder, scene_seed, camera_rigs) need_terrain_processing = "atmosphere" in bpy.data.objects @@ -267,10 +272,9 @@ def execute_tasks( whole_bbox=info["whole_bbox"], ) - cameras = [cam_util.get_camera(i, j) for i, j in cam_util.get_cameras_ids()] terrain.fine_terrain( output_folder, - cameras=cameras, + cameras=[c for rig in camera_rigs for c in rig.children], optimize_terrain_diskusage=optimize_terrain_diskusage, ) @@ -326,7 +330,7 @@ def execute_tasks( render( scene_seed, output_folder=output_folder, - camera_id=camera_id, + camera=active_camera, resample_idx=resample_idx, ) diff --git a/infinigen/core/placement/camera.py b/infinigen/core/placement/camera.py index 7f796a370..b4856e7ff 100644 --- a/infinigen/core/placement/camera.py +++ b/infinigen/core/placement/camera.py @@ -40,8 +40,6 @@ logger = logging.getLogger(__name__) -CAMERA_RIGS_DIRNAME = "CameraRigs" - @gin.configurable def get_sensor_coords(cam, H, W, sparse=False): @@ -119,55 +117,54 @@ def spawn_camera(): return cam -def camera_name(rig_id, cam_id): - return f"{CAMERA_RIGS_DIRNAME}/{rig_id}/{cam_id}" +def cam_name(cam_rig, subcam): + return f"camera_{cam_rig}_{subcam}" + + +def get_id(camera: bpy.types.Object): + _, rig, subcam = camera.name.split("_") + return int(rig), int(subcam) @gin.configurable def spawn_camera_rigs( camera_rig_config, n_camera_rigs, -): +) -> list[bpy.types.Object]: + rigs_col = butil.get_collection("camera_rigs") + cams_col = butil.get_collection("cameras") + def spawn_rig(i): - rig_parent = butil.spawn_empty(f"{CAMERA_RIGS_DIRNAME}/{i}") + rig_parent = butil.spawn_empty(f"camrig.{i}") + butil.put_in_collection(rig_parent, rigs_col) + for j, config in enumerate(camera_rig_config): cam = spawn_camera() - cam.name = camera_name(i, j) + cam.name = cam_name(i, j) cam.parent = rig_parent - cam.location = config["loc"] cam.rotation_euler = config["rot_euler"] - return rig_parent + butil.put_in_collection(cam, cams_col) - camera_rigs = [spawn_rig(i) for i in range(n_camera_rigs)] - butil.group_in_collection(camera_rigs, CAMERA_RIGS_DIRNAME) + return rig_parent - return camera_rigs + return [spawn_rig(i) for i in range(n_camera_rigs)] -def get_cameras_ids() -> list[tuple]: - res = [] - col = bpy.data.collections[CAMERA_RIGS_DIRNAME] - rigs = [o for o in col.objects if o.name.count("/") == 1] - for i, root in enumerate(rigs): - for j, subcam in enumerate(root.children): - assert subcam.name == camera_name(i, j) - res.append((i, j)) +def get_camera_rigs() -> list[bpy.types.Object]: + if "camera_rigs" not in bpy.data.collections: + raise ValueError("No camera rigs found") - return res + result = list(bpy.data.collections["camera_rigs"].objects) + for i, rig in enumerate(result): + for j, child in enumerate(rig.children): + expected = cam_name(i, j) + if child.name != expected: + raise ValueError(f"child {i=} {j} was {child.name=}, {expected=}") -def get_camera(rig_id, subcam_id, checkonly=False): - col = bpy.data.collections[CAMERA_RIGS_DIRNAME] - name = camera_name(rig_id, subcam_id) - if name in col.objects.keys(): - return col.objects[name] - if checkonly: - return None - raise ValueError( - f"Could not get_camera({rig_id=}, {subcam_id=}). {list(col.objects.keys())=}" - ) + return result @node_utils.to_nodegroup( @@ -181,8 +178,7 @@ def nodegroup_active_cam_info(nw: NodeWrangler): ) -def set_active_camera(rig_id, subcam_id): - camera = get_camera(rig_id, subcam_id) +def set_active_camera(camera: bpy.types.Object): bpy.context.scene.camera = camera ng = ( @@ -193,34 +189,6 @@ def set_active_camera(rig_id, subcam_id): return bpy.context.scene.camera -def positive_gaussian(mean, std): - while True: - val = np.random.normal(mean, std) - if val > 0: - return val - - -def set_camera( - camera, - location, - rotation, - focus_dist, - frame, -): - camera.location = location - camera.rotation_euler = rotation - if focus_dist is not None: - camera.data.dof.focus_distance = ( - focus_dist # this should come before view_layer.update() - ) - bpy.context.view_layer.update() - - camera.keyframe_insert(data_path="location", frame=frame) - camera.keyframe_insert(data_path="rotation_euler", frame=frame) - if focus_dist is not None: - camera.data.dof.keyframe_insert(data_path="focus_distance", frame=frame) - - def terrain_camera_query( cam, scene_bvh, terrain_tags_queries, vertexwise_min_dist, min_dist=0 ): @@ -465,7 +433,7 @@ def compute_base_views( ): potential_views = [] n_min_candidates = int(min_candidates_ratio * n_views) - logger.debug("Center Coordinate", center_coordinate) + with tqdm(total=n_min_candidates, desc="Searching for camera viewpoints") as pbar: for it in range(1, max_tries): if center_coordinate: @@ -798,32 +766,37 @@ def animate_cameras( @gin.configurable -def save_camera_parameters(camera_ids, output_folder, frame, use_dof=False): +def save_camera_parameters( + camera_obj: bpy.types.Object, output_folder, frame, use_dof=False +): output_folder = Path(output_folder) output_folder.mkdir(exist_ok=True, parents=True) + if frame is not None: bpy.context.scene.frame_set(frame) - for camera_pair_id, camera_id in camera_ids: - camera_obj = get_camera(camera_pair_id, camera_id) - if use_dof is not None: - camera_obj.data.dof.use_dof = use_dof - # Saving camera parameters - K = camera.get_calibration_matrix_K_from_blender(camera_obj.data) - suffix = get_suffix( - dict(cam_rig=camera_pair_id, resample=0, frame=frame, subcam=camera_id) - ) - output_file = output_folder / f"camview{suffix}.npz" - height_width = np.array( - ( - bpy.context.scene.render.resolution_y, - bpy.context.scene.render.resolution_x, - ) + camrig_id, subcam_id = get_id(camera_obj) + + if use_dof is not None: + camera_obj.data.dof.use_dof = use_dof + + # Saving camera parameters + K = camera.get_calibration_matrix_K_from_blender(camera_obj.data) + suffix = get_suffix( + dict(cam_rig=camrig_id, resample=0, frame=frame, subcam=subcam_id) + ) + output_file = output_folder / f"camview{suffix}.npz" + + height_width = np.array( + ( + bpy.context.scene.render.resolution_y, + bpy.context.scene.render.resolution_x, ) - T = np.asarray(camera_obj.matrix_world, dtype=np.float64) @ np.diag( - (1.0, -1.0, -1.0, 1.0) - ) # Y down Z forward (aka opencv) - np.savez(output_file, K=np.asarray(K, dtype=np.float64), T=T, HW=height_width) + ) + T = np.asarray(camera_obj.matrix_world, dtype=np.float64) @ np.diag( + (1.0, -1.0, -1.0, 1.0) + ) # Y down Z forward (aka opencv) + np.savez(output_file, K=np.asarray(K, dtype=np.float64), T=T, HW=height_width) if __name__ == "__main__": diff --git a/infinigen/core/placement/placement.py b/infinigen/core/placement/placement.py index ed6718ba7..5e579177c 100644 --- a/infinigen/core/placement/placement.py +++ b/infinigen/core/placement/placement.py @@ -18,9 +18,8 @@ NodeWrangler, geometry_node_group_empty_new, ) -from infinigen.core.placement import detail +from infinigen.core.placement import detail, split_in_view from infinigen.core.util import blender as butil -from infinigen.core.util import camera as camera_util from .factory import AssetFactory @@ -151,7 +150,7 @@ def parse_asset_name(name): def populate_collection( factory: AssetFactory, - placeholder_col, + placeholder_col: bpy.types.Collection, asset_col_target=None, cameras=None, dist_cull=None, @@ -178,33 +177,18 @@ def populate_collection( continue if cameras is not None: - populate = False - dist_list = [] - vis_dist_list = [] - for i, camera in enumerate(cameras): - points = get_placeholder_points(p) - dists, vis_dists = camera_util.min_dists_from_cam_trajectory( - points, camera + mask, min_dists, min_vis_dists = split_in_view.compute_inview_distances( + get_placeholder_points(p), cameras, verbose=verbose + ) + + dist = min_dists.min() + vis_dist = min_vis_dists.min() + + if not mask.any(): + logger.debug( + f"{p.name=} culled, not in view of any camera. {dist=} {vis_dist=}" ) - dist, vis_dist = dists.min(), vis_dists.min() - if dist_cull is not None and dist > dist_cull: - logger.debug( - f"{p.name=} temporarily culled in camera {i} due to {dist=:.2f} > {dist_cull=}" - ) - continue - if vis_cull is not None and vis_dist > vis_cull: - logger.debug( - f"{p.name=} temporarily culled in camera {i} due to {vis_dist=:.2f} > {vis_cull=}" - ) - continue - populate = True - dist_list.append(dist) - vis_dist_list.append(vis_dist) - if not populate: - p.hide_render = True continue - p["dist"] = min(dist_list) - p["vis_dist"] = min(vis_dist_list) else: dist = detail.scatter_res_distance() @@ -251,7 +235,12 @@ def populate_collection( @gin.configurable def populate_all( - factory_class, camera, dist_cull=200, vis_cull=0, cache_system=None, **kwargs + factory_class: type, + cameras: list[bpy.types.Object], + dist_cull=200, + vis_cull=0, + cache_system=None, + **kwargs, ): """ Find all collections that may have been produced by factory_class, and update them @@ -283,7 +272,7 @@ def populate_all( factory_class(int(fac_seed), **kwargs), col, asset_target_col, - camera, + camera=cameras, dist_cull=dist_cull, vis_cull=vis_cull, cache_system=cache_system, diff --git a/infinigen/core/placement/split_in_view.py b/infinigen/core/placement/split_in_view.py index aaae50615..56790b849 100644 --- a/infinigen/core/placement/split_in_view.py +++ b/infinigen/core/placement/split_in_view.py @@ -8,6 +8,7 @@ import bpy import numpy as np +from mathutils import Matrix from mathutils.bvhtree import BVHTree from tqdm import trange @@ -15,9 +16,18 @@ from infinigen.core.util import blender as butil from infinigen.core.util import camera as cam_util from infinigen.core.util.logging import Suppress +from infinigen.core.util.math import dehomogenize, homogenize +logger = logging.getLogger(__name__) -def raycast_visiblity_mask(obj, cam, start=None, end=None, verbose=True): + +def raycast_visiblity_mask( + obj: bpy.types.Object, + cameras: list[bpy.types.Object], + start=None, + end=None, + verbose=True, +): bvh = BVHTree.FromObject(obj, bpy.context.evaluated_depsgraph_get()) if start is None: @@ -30,19 +40,20 @@ def raycast_visiblity_mask(obj, cam, start=None, end=None, verbose=True): for i in rangeiter(start, end + 1): bpy.context.scene.frame_set(i) invworld = obj.matrix_world.inverted() - sensor_coords, pix_it = get_sensor_coords(cam) - for x, y in pix_it: - direction = ( - sensor_coords[y, x] - cam.matrix_world.translation - ).normalized() - origin = cam.matrix_world.translation - _, _, index, dist = bvh.ray_cast( - invworld @ origin, invworld.to_3x3() @ direction - ) - if dist is None: - continue - for vi in obj.data.polygons[index].vertices: - mask[vi] = True + for cam in cameras: + sensor_coords, pix_it = get_sensor_coords(cam) + for x, y in pix_it: + direction = ( + sensor_coords[y, x] - cam.matrix_world.translation + ).normalized() + origin = cam.matrix_world.translation + _, _, index, dist = bvh.ray_cast( + invworld @ origin, invworld.to_3x3() @ direction + ) + if dist is None: + continue + for vi in obj.data.polygons[index].vertices: + mask[vi] = True return mask @@ -74,42 +85,113 @@ def duplicate_mask(obj, mask, dilate=0, invert=False): return butil.spawn_point_cloud("duplicate_mask", [], []) +def compute_vis_dists(points: np.array, cam: bpy.types.Object): + projmat, K, RT = map(np.array, cam_util.get_3x4_P_matrix_from_blender(cam)) + proj = points @ projmat.T + uv, d = dehomogenize(proj), proj[:, -1] + + clamped_uv = np.clip(uv, [0, 0], butil.get_camera_res()) + clamped_d = np.maximum(d, 0) + + RT_4x4_inv = np.array(Matrix(RT).to_4x4().inverted()) + clipped_pos = ( + homogenize((homogenize(clamped_uv) * clamped_d[:, None]) @ np.linalg.inv(K).T) + @ RT_4x4_inv.T + ) + + vis_dist = np.linalg.norm(points[:, :-1] - clipped_pos[:, :-1], axis=-1) + + return d, vis_dist + + +def compute_inview_distances( + points: np.array, + cameras: list[bpy.types.Object], + dist_max, + vis_margin, + frame_start=None, + frame_end=None, + verbose=False, +): + """ + Compute the minimum distance of each point to any of the cameras in the scene. + + Parameters: + - points: an array of 3D points, in world space + - cameras: a list of cameras in the scene + - dist_max: the maximum distance to consider a point "in view" + - vis_margin: how far outside the view frustum to consider a point "in view" + + Returns: + - mask: boolean array of whether each point is within within vis_margin and dist_max of any frame of any camera + - min_dists: the distance of each point the closest camera + - min_vis_dists: the distance of each point to the nearest point in any camera's view frustum + """ + + assert len(points.shape) == 2 and points.shape[-1] == 3 + + if frame_start is None: + frame_start = bpy.context.scene.frame_start + if frame_end is None: + frame_end = bpy.context.scene.frame_end + + points = homogenize(points) + + mask = np.zeros(len(points), dtype=bool) + min_dists = np.full(len(points), 1e7) + min_vis_dists = np.full(len(points), 1e7) + + rangeiter = trange if verbose else range + + assert frame_start < frame_end + 1, (frame_start, frame_end) + + for frame in rangeiter(frame_start, frame_end + 1): + bpy.context.scene.frame_set(frame) + for cam in cameras: + dists, vis_dists = compute_vis_dists(points, cam) + mask |= (dists < dist_max) & (vis_dists < vis_margin) + if mask.any(): + min_vis_dists[mask] = np.minimum(vis_dists[mask], min_vis_dists[mask]) + min_dists[mask] = np.minimum(dists[mask], min_dists[mask]) + + logger.debug(f"Computed dists for {frame=} {cam.name} {mask.mean()=:.2f}") + + return mask, min_dists, min_vis_dists + + def split_inview( obj: bpy.types.Object, - cam, - vis_margin, - raycast=False, - dilate=0, - dist_max=1e7, + cameras: list[bpy.types.Object], + dist_max: float = 1e7, + vis_margin: float = 0, + raycast: bool = False, + dilate: float = 0, outofview=True, verbose=False, - print_areas=False, hide_render=None, suffix=None, **kwargs, ): assert obj.type == "MESH" - assert cam.type == "CAMERA" bpy.context.view_layer.update() verts = np.zeros((len(obj.data.vertices), 3)) obj.data.vertices.foreach_get("co", verts.reshape(-1)) verts = butil.apply_matrix_world(obj, verts) - dists, vis_dists = cam_util.min_dists_from_cam_trajectory( - verts, cam, verbose=verbose, **kwargs + mask, dists, vis_dists = compute_inview_distances( + verts, + cameras, + dist_max=dist_max, + vis_margin=vis_margin, + verbose=verbose, + **kwargs, ) - vis_mask = vis_dists < vis_margin - dist_mask = dists < dist_max - mask = vis_mask * dist_mask - - logging.debug( - f"split_inview {vis_mask.mean()=:.2f} {dist_mask.mean()=:.2f} {mask.mean()=:.2f}" - ) + logger.debug(f"split_inview {suffix=} {dist_max=} {vis_margin=} {mask.mean()=:.2f}") if raycast: - mask *= raycast_visiblity_mask(obj, cam) + mask *= raycast_visiblity_mask(obj, cameras) inview = duplicate_mask(obj, mask, dilate=dilate) @@ -118,13 +200,6 @@ def split_inview( else: outview = butil.spawn_point_cloud("duplicate_mask", [], []) - if print_areas: - sa_in = butil.surface_area(inview) - sa_out = butil.surface_area(outview) - print( - f"split {obj.name=} into inview area {sa_in:.2f} and outofview area {sa_out:.2f}" - ) - inview.name = obj.name + ".inview" outview.name = obj.name + ".outofview" diff --git a/infinigen/core/rendering/render.py b/infinigen/core/rendering/render.py index 8626fa764..75bfa9da9 100644 --- a/infinigen/core/rendering/render.py +++ b/infinigen/core/rendering/render.py @@ -377,7 +377,7 @@ def configure_compositor( @gin.configurable def render_image( - camera_id, + camera: bpy.types.Object, frames_folder, passes_to_save, flat_shading=False, @@ -388,8 +388,6 @@ def render_image( ): tic = time.time() - camera_rig_id, subcam_id = camera_id - for exclude in excludes: bpy.data.objects[exclude].hide_render = True @@ -399,6 +397,8 @@ def render_image( tmp_dir.mkdir(exist_ok=True) bpy.context.scene.render.filepath = f"{tmp_dir}{os.sep}" + camrig_id, subcam_id = cam_util.get_id(camera) + if flat_shading: with Timer("Set object indices"): object_data = set_pass_indices() @@ -406,7 +406,7 @@ def render_image( first_frame = bpy.context.scene.frame_start suffix = get_suffix( dict( - cam_rig=camera_rig_id, + cam_rig=camrig_id, resample=0, frame=first_frame, subcam=subcam_id, @@ -425,7 +425,7 @@ def render_image( first_frame = bpy.context.scene.frame_start suffix = get_suffix( dict( - cam_rig=camera_rig_id, + cam_rig=camrig_id, resample=0, frame=first_frame, subcam=subcam_id, @@ -437,17 +437,16 @@ def render_image( bpy.context.scene.use_nodes = True file_slot_nodes = configure_compositor(frames_folder, passes_to_save, flat_shading) - indices = dict(cam_rig=camera_rig_id, resample=0, subcam=subcam_id) + indices = dict(cam_rig=camrig_id, resample=0, subcam=subcam_id) ## Update output names fileslot_suffix = get_suffix({"frame": "####", **indices}) for file_slot in file_slot_nodes: file_slot.path = f"{file_slot.path}{fileslot_suffix}" - camera = cam_util.get_camera(camera_rig_id, subcam_id) if use_dof == "IF_TARGET_SET": use_dof = camera.data.dof.focus_object is not None - if use_dof is not None: + elif use_dof is not None: camera.data.dof.use_dof = use_dof camera.data.dof.aperture_fstop = dof_aperture_fstop @@ -470,7 +469,7 @@ def render_image( postprocess_blendergt_outputs(frames_folder, suffix) else: cam_util.save_camera_parameters( - camera_ids=cam_util.get_cameras_ids(), + camera, output_folder=frames_folder, frame=frame, ) diff --git a/infinigen/core/util/camera.py b/infinigen/core/util/camera.py index beb04dd74..fe58f8da7 100644 --- a/infinigen/core/util/camera.py +++ b/infinigen/core/util/camera.py @@ -3,15 +3,14 @@ # Authors: Lahav Lipson, Lingjie Mei +import logging import bpy import bpy_extras import numpy as np from mathutils import Matrix, Vector -from tqdm import trange -from infinigen.core.util import blender as butil -from infinigen.core.util.math import dehomogenize, homogenize +logger = logging.getLogger(__name__) # --------------------------------------------------------------- # 3x4 P matrix from Blender camera @@ -133,48 +132,6 @@ def project_by_object_utils(cam, point): return Vector((co_2d.x * render_size[0], render_size[1] - co_2d.y * render_size[1])) -def compute_vis_dists(points, cam): - projmat, K, RT = map(np.array, get_3x4_P_matrix_from_blender(cam)) - proj = points @ projmat.T - uv, d = dehomogenize(proj), proj[:, -1] - - clamped_uv = np.clip(uv, [0, 0], butil.get_camera_res()) - clamped_d = np.maximum(d, 0) - - RT_4x4_inv = np.array(Matrix(RT).to_4x4().inverted()) - clipped_pos = ( - homogenize((homogenize(clamped_uv) * clamped_d[:, None]) @ np.linalg.inv(K).T) - @ RT_4x4_inv.T - ) - - vis_dist = np.linalg.norm(points[:, :-1] - clipped_pos[:, :-1], axis=-1) - - return d, vis_dist - - -def min_dists_from_cam_trajectory(points, cam, start=None, end=None, verbose=False): - assert len(points.shape) == 2 and points.shape[-1] == 3 - assert cam.type == "CAMERA" - - if start is None: - start = bpy.context.scene.frame_start - if end is None: - end = bpy.context.scene.frame_end - - points = homogenize(points) - min_dists = np.full(len(points), 1e7) - min_vis_dists = np.full(len(points), 1e7) - - rangeiter = trange if verbose else range - for i in rangeiter(start, end + 1): - bpy.context.scene.frame_set(i) - dists, vis_dists = compute_vis_dists(points, cam) - min_dists = np.minimum(dists, min_dists) - min_vis_dists = np.minimum(vis_dists, min_vis_dists) - - return min_dists, min_vis_dists - - def points_inview(bbox, camera): proj = np.array(get_3x4_P_matrix_from_blender(camera)[0]) x, y, z = proj @ np.concatenate([bbox, np.ones((len(bbox), 1))], -1).T diff --git a/infinigen_examples/configs_nature/multiview_stereo.gin b/infinigen_examples/configs_nature/multiview_stereo.gin index a56b1fab4..c3ae64227 100644 --- a/infinigen_examples/configs_nature/multiview_stereo.gin +++ b/infinigen_examples/configs_nature/multiview_stereo.gin @@ -1,9 +1,9 @@ -camera.spawn_camera_rigs.n_camera_rigs = 30 -camera.spawn_camera_rigs.camera_rig_config = [ - {'loc': (0, 0, 0), 'rot_euler': (0, 0, 0)}, -] +compute_base_views.min_candidates_ratio = 1 +fine_terrain.mesher_backend = "OcMesher" + configure_cameras.mvs_setting = True compose_nature.camera_selection_ranges_ratio = {} compose_nature.camera_selection_tags_ratio = {} -compute_base_views.min_candidates_ratio = 1 -fine_terrain.mesher_backend = "OcMesher" \ No newline at end of file +compose_nature.animate_cameras_enabled=False + +compose_nature.inview_distance = 40 \ No newline at end of file diff --git a/infinigen_examples/generate_asset_demo.py b/infinigen_examples/generate_asset_demo.py index bba4504aa..49afcd0bb 100644 --- a/infinigen_examples/generate_asset_demo.py +++ b/infinigen_examples/generate_asset_demo.py @@ -112,7 +112,7 @@ def compose_scene( kole_clouds.add_kole_clouds() camera_rigs = cam_util.spawn_camera_rigs() - cam = cam_util.get_camera(0, 0) + cam = camera_rigs[0].children[0] # find a flat spot on the terrain to do the demo\ terrain = Terrain( @@ -172,7 +172,7 @@ def compose_scene( # apply a procedural backdrop on all visible parts of the terrain terrain_inview, *_ = split_inview( - terrain_mesh, cam=cam, dist_max=params["inview_distance"], vis_margin=2 + terrain_mesh, cameras=[cam], dist_max=params["inview_distance"], vis_margin=2 ) if background is None: pass diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py index 7f73f80d3..dbf20108b 100644 --- a/infinigen_examples/generate_indoors.py +++ b/infinigen_examples/generate_indoors.py @@ -310,7 +310,7 @@ def place_floating(): placer = FloatingObjectPlacement( generators=facs, - camera=cam_util.get_camera(0, 0), + camera=camera_rigs[0].children[0], background_objs=list(pholder_cutters.objects) + list(pholder_rooms.objects), collision_objs=list(pholder_objs.objects), ) @@ -389,8 +389,6 @@ def place_floating(): # state.print() state.to_json(output_folder / "solve_state.json") - cam = cam_util.get_camera(0, 0) - def turn_off_lights(): for o in bpy.data.objects: if o.type == "LIGHT" and not o.data.cycles.is_portal: @@ -431,7 +429,7 @@ def invisible_room_ceilings(): create_outdoor_backdrop, terrain, house_bbox=house_bbox, - cam=cam, + cameras=[rig.children[0] for rig in camera_rigs], p=p, params=overrides, use_chance=False, diff --git a/infinigen_examples/generate_nature.py b/infinigen_examples/generate_nature.py index f982c49de..d04a1b790 100644 --- a/infinigen_examples/generate_nature.py +++ b/infinigen_examples/generate_nature.py @@ -293,20 +293,25 @@ def camera_preprocess(): ), use_chance=False, ) - cam = cam_util.get_camera(0, 0) + primary_cams = [rig.children[0] for rig in camera_rigs] - p.run_stage("lighting", lighting.sky_lighting.add_lighting, cam, use_chance=False) + p.run_stage( + "lighting", + lighting.sky_lighting.add_lighting, + primary_cams[0], + use_chance=False, + ) # determine a small area of the terrain for the creatures to run around on # must happen before camera is animated, as camera may want to follow them around terrain_center, *_ = split_in_view.split_inview( terrain_mesh, - cam=cam, - start=0, - end=0, - outofview=False, - vis_margin=5, + primary_cams, dist_max=params["center_distance"], + vis_margin=5, + frame_start=0, + frame_end=0, + outofview=False, hide_render=True, suffix="center", ) @@ -367,10 +372,9 @@ def animate_cameras(): with logging_util.Timer("Compute coarse terrain frustrums"): terrain_inview, *_ = split_in_view.split_inview( terrain_mesh, + primary_cams, verbose=True, outofview=False, - print_areas=True, - cam=cam, vis_margin=2, dist_max=params["inview_distance"], hide_render=True, @@ -378,10 +382,9 @@ def animate_cameras(): ) terrain_near, *_ = split_in_view.split_inview( terrain_mesh, + primary_cams, verbose=True, outofview=False, - print_areas=True, - cam=cam, vis_margin=2, dist_max=params["near_distance"], hide_render=True, @@ -734,9 +737,12 @@ def add_tilted_river(): @gin.configurable -def populate_scene(output_folder, scene_seed, **params): +def populate_scene( + output_folder: Path, scene_seed: int, camera_rigs: list[bpy.types.Object], **params +): p = RandomStageExecutor(scene_seed, output_folder, params) - camera = [cam_util.get_camera(i, j) for i, j in cam_util.get_cameras_ids()] + + primary_cams = [rig.children[0] for rig in camera_rigs] season = p.run_stage( "choose_season", trees.random_season, use_chance=False, default=[] @@ -750,7 +756,7 @@ def populate_scene(output_folder, scene_seed, **params): use_chance=False, default=[], fn=lambda: placement.populate_all( - trees.TreeFactory, camera, season=season, vis_cull=4 + trees.TreeFactory, primary_cams, season=season, vis_cull=4 ), ) # , # meshing_camera=camera, adapt_mesh_method='subdivide', cam_meshing_max_dist=8)) @@ -758,40 +764,44 @@ def populate_scene(output_folder, scene_seed, **params): "populate_boulders", use_chance=False, default=[], - fn=lambda: placement.populate_all(rocks.BoulderFactory, camera, vis_cull=3), + fn=lambda: placement.populate_all( + rocks.BoulderFactory, primary_cams, vis_cull=3 + ), ) # , # meshing_camera=camera, adapt_mesh_method='subdivide', cam_meshing_max_dist=8)) populated["bushes"] = p.run_stage( "populate_bushes", use_chance=False, fn=lambda: placement.populate_all( - trees.BushFactory, camera, vis_cull=1, adapt_mesh_method="subdivide" + trees.BushFactory, primary_cams, vis_cull=1, adapt_mesh_method="subdivide" ), ) p.run_stage( "populate_kelp", use_chance=False, fn=lambda: placement.populate_all( - monocot.KelpMonocotFactory, camera, vis_cull=5 + monocot.KelpMonocotFactory, primary_cams, vis_cull=5 ), ) populated["cactus"] = p.run_stage( "populate_cactus", use_chance=False, - fn=lambda: placement.populate_all(cactus.CactusFactory, camera, vis_cull=6), + fn=lambda: placement.populate_all( + cactus.CactusFactory, primary_cams, vis_cull=6 + ), ) p.run_stage( "populate_clouds", use_chance=False, fn=lambda: placement.populate_all( - cloud.CloudFactory, camera, dist_cull=None, vis_cull=None + cloud.CloudFactory, primary_cams, dist_cull=None, vis_cull=None ), ) p.run_stage( "populate_glowing_rocks", use_chance=False, fn=lambda: placement.populate_all( - rocks.GlowingRocksFactory, camera, dist_cull=None, vis_cull=None + rocks.GlowingRocksFactory, primary_cams, dist_cull=None, vis_cull=None ), ) @@ -801,7 +811,7 @@ def populate_scene(output_folder, scene_seed, **params): default=[], fn=lambda: placement.populate_all( fluid.CachedTreeFactory, - camera, + primary_cams, season=season, vis_cull=4, dist_cull=70, @@ -814,7 +824,7 @@ def populate_scene(output_folder, scene_seed, **params): default=[], fn=lambda: placement.populate_all( fluid.CachedBoulderFactory, - camera, + primary_cams, vis_cull=3, dist_cull=70, cache_system=fire_cache_system, @@ -825,7 +835,7 @@ def populate_scene(output_folder, scene_seed, **params): use_chance=False, fn=lambda: placement.populate_all( fluid.CachedBushFactory, - camera, + primary_cams, vis_cull=1, adapt_mesh_method="subdivide", cache_system=fire_cache_system, @@ -836,7 +846,7 @@ def populate_scene(output_folder, scene_seed, **params): use_chance=False, fn=lambda: placement.populate_all( fluid.CachedCactusFactory, - camera, + primary_cams, vis_cull=6, cache_system=fire_cache_system, ), @@ -909,7 +919,7 @@ def apply_snow_layer(surface_cls): p.run_stage( f"populate_{k}", use_chance=False, - fn=lambda: placement.populate_all(fac, camera=None), + fn=lambda: placement.populate_all(fac, cameras=None), ) fire_warmup = params.get("fire_warmup", 50) diff --git a/infinigen_examples/util/generate_indoors_util.py b/infinigen_examples/util/generate_indoors_util.py index 2e60daf4f..c85717927 100644 --- a/infinigen_examples/util/generate_indoors_util.py +++ b/infinigen_examples/util/generate_indoors_util.py @@ -44,7 +44,7 @@ def within_bbox_2d(verts, bbox): def create_outdoor_backdrop( terrain: Terrain, house_bbox: tuple, - cam, + cameras: list[bpy.types.Object], p: pipeline.RandomStageExecutor, params: dict, ): @@ -86,10 +86,9 @@ def create_outdoor_backdrop( terrain_inview, *_ = split_in_view.split_inview( main_terrain, + cameras, verbose=True, outofview=False, - print_areas=True, - cam=cam, vis_margin=2, dist_max=params["near_distance"], hide_render=True,