| import pytorch3d |
| import torch |
| import imageio |
| import numpy as np |
| import os |
| from pytorch3d.io import load_objs_as_meshes |
| from pytorch3d.renderer import ( |
| AmbientLights, |
| PerspectiveCameras, |
| RasterizationSettings, |
| look_at_view_transform, |
| TexturesVertex, |
| MeshRenderer, |
| Materials, |
| MeshRasterizer, |
| SoftPhongShader, |
| PointLights |
| ) |
| import trimesh |
| from tqdm import tqdm |
| from pytorch3d.transforms import RotateAxisAngle |
|
|
| from shader import MultiOutputShader |
|
|
| def _rgb_to_srgb(f: torch.Tensor) -> torch.Tensor: |
| return torch.where(f <= 0.0031308, f * 12.92, torch.pow(torch.clamp(f, 0.0031308), 1.0/2.4)*1.055 - 0.055) |
|
|
| def rgb_to_srgb(f: torch.Tensor) -> torch.Tensor: |
| assert f.shape[-1] == 3 or f.shape[-1] == 4 |
| out = torch.cat((_rgb_to_srgb(f[..., 0:3]), f[..., 3:4]), dim=-1) if f.shape[-1] == 4 else _rgb_to_srgb(f) |
| assert out.shape[0] == f.shape[0] and out.shape[1] == f.shape[1] |
| return out |
|
|
| def render_video_from_obj(input_obj_path, output_video_path, num_frames=60, image_size=512, fps=15, device="cuda"): |
| if not os.path.exists(input_obj_path): |
| raise FileNotFoundError(f"Input OBJ file not found: {input_obj_path}") |
|
|
| scene_data = trimesh.load(input_obj_path) |
|
|
| if isinstance(scene_data, trimesh.Scene): |
| mesh_data = trimesh.util.concatenate([geom for geom in scene_data.geometry.values()]) |
| else: |
| mesh_data = scene_data |
|
|
| if not hasattr(mesh_data, 'vertex_normals') or mesh_data.vertex_normals is None: |
| mesh_data.compute_vertex_normals() |
|
|
| vertices = torch.tensor(mesh_data.vertices, dtype=torch.float32, device=device) |
| faces = torch.tensor(mesh_data.faces, dtype=torch.int64, device=device) |
|
|
| if mesh_data.visual.vertex_colors is None: |
| vertex_colors = torch.ones_like(vertices)[None] |
| else: |
| vertex_colors = torch.tensor(mesh_data.visual.vertex_colors[:, :3], dtype=torch.float32)[None] |
| textures = TexturesVertex(verts_features=vertex_colors) |
| textures.to(device) |
| mesh = pytorch3d.structures.Meshes(verts=[vertices], faces=[faces], textures=textures) |
|
|
| lights = AmbientLights(ambient_color=((2.0,)*3,), device=device) |
| |
| raster_settings = RasterizationSettings( |
| image_size=image_size, |
| blur_radius=0.0, |
| faces_per_pixel=1, |
| ) |
|
|
| frames = [] |
| camera_distance = 6.5 |
| elevs = 0.0 |
| center = (0.0, 0.0, 0.0) |
| materials = Materials( |
| device=device, |
| diffuse_color=((1.0, 1.0, 1.0),), |
| ambient_color=((1.0, 1.0, 1.0),), |
| specular_color=((1.0, 1.0, 1.0),), |
| shininess=0.0, |
| ) |
| |
| rasterizer = MeshRasterizer(raster_settings=raster_settings) |
| for i in tqdm(range(num_frames)): |
| azims = 360.0 * i / num_frames |
| R, T = look_at_view_transform( |
| dist=camera_distance, |
| elev=elevs, |
| azim=azims, |
| at=(center,), |
| degrees=True |
| ) |
|
|
| |
| cameras = PerspectiveCameras(device=device, R=R, T=T, focal_length=5.0) |
| cameras.znear = 0.0001 |
| cameras.zfar = 10000000.0 |
| shader=MultiOutputShader( |
| device=device, |
| cameras=cameras, |
| lights=lights, |
| materials=materials, |
| choices=["rgb", "mask", "normal", "albedo"] |
| ) |
|
|
| renderer = MeshRenderer(rasterizer=rasterizer, shader=shader) |
| render_result = renderer(mesh, cameras=cameras) |
|
|
| render_result["albedo"] = rgb_to_srgb(render_result["albedo"]/255.0)*255.0 |
| rgb_image = render_result["albedo"] * render_result["mask"] + (1 - render_result["mask"]) * torch.ones_like(render_result["albedo"]) * 255.0 |
| normal_map = render_result["normal"] |
|
|
| rgb = rgb_image[0, ..., :3].cpu().numpy() |
| normal_map = torch.nn.functional.normalize(normal_map, dim=-1) |
| normal_map = (normal_map + 1) / 2 |
| normal_map = normal_map * render_result["mask"] + (1 - render_result["mask"]) * torch.ones_like(render_result["normal"]) |
| normal = normal_map[0, ..., :3].cpu().numpy() |
| rgb = np.clip(rgb, 0, 255).astype(np.uint8) |
| normal = np.clip(normal*255, 0, 255).astype(np.uint8) |
| combined_image = np.concatenate((rgb, normal), axis=1) |
|
|
| frames.append(combined_image) |
|
|
| imageio.mimsave(output_video_path, frames, fps=fps) |
|
|
| print(f"Video saved to {output_video_path}") |
|
|
| if __name__ == '__main__': |
| input_obj_path = "./354e2aee-091d-4dc6-bdb1-e09be5791218_isomer_recon_mesh.obj" |
| output_video_path = "output.mp4" |
| render_video_from_obj(input_obj_path, output_video_path) |