File size: 5,142 Bytes
1e5535f
 
02a9751
1e5535f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
02a9751
 
1e5535f
 
 
 
4df757e
02a9751
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1e5535f
 
 
 
02a9751
1e5535f
 
 
 
 
 
 
 
 
 
 
 
 
ded6c2a
1e5535f
 
 
 
 
 
02a9751
 
1e5535f
 
 
 
 
02a9751
1e5535f
 
 
 
 
 
 
02a9751
1e5535f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
02a9751
 
 
1e5535f
02a9751
 
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
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 render_video_from_obj(input_obj_path, output_video_path, num_frames=60, image_size=512, fps=30, device="cuda"):
    if not os.path.exists(input_obj_path):
        raise FileNotFoundError(f"Input OBJ file not found: {input_obj_path}")

    # 加载3D模型
    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)
    vertex_normals = torch.tensor(mesh_data.vertex_normals, dtype=torch.float32)

    # 获取顶点颜色
    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对象
    mesh = pytorch3d.structures.Meshes(verts=[vertices], faces=[faces], textures=textures)

    # 设置渲染器
    lights = AmbientLights(ambient_color=((2.0,)*3,), device=device)
    # lights = PointLights(device=device, location=[[0.0, 0.0, 3.0]], ambient_color=[[0.5, 0.5, 0.5]], diffuse_color=[[1.0, 1.0, 1.0]])
    raster_settings = RasterizationSettings(
        image_size=image_size,  # 渲染图像的尺寸
        blur_radius=0.0,  # 默认无模糊
        faces_per_pixel=1,  # 每像素渲染一个面
        # background_color=(1.0, 1.0, 1.0)
    )

    # 设置旋转和渲染参数
    frames = []
    camera_distance = 6.5
    elevs = 0.0
    center = (0.0, 0.0, 0.0)
    # 渲染每一帧
    materials = Materials(
            device=device,
            diffuse_color=((0.0, 0.0, 0.0),),
            ambient_color=((1.0, 1.0, 1.0),),
            specular_color=((0.0, 0.0, 0.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"]
            )

        renderer = MeshRenderer(rasterizer=rasterizer, shader=shader)
        # 渲染RGB图像和Normal图像
        render_result = renderer(mesh, cameras=cameras)
        rgb_image = render_result["rgb"] * render_result["mask"] + (1 - render_result["mask"]) * torch.ones_like(render_result["rgb"]) * 255.0
        normal_map = render_result["normal"]

        # 提取RGB和Normal map
        rgb = rgb_image[0, ..., :3].cpu().numpy()  # RGB图像
        normal_map = torch.nn.functional.normalize(normal_map, dim=-1)  # Normal map
        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()  # Normal map
        rgb = np.clip(rgb, 0, 255).astype(np.uint8)
        normal = np.clip(normal*255, 0, 255).astype(np.uint8)
        # 将RGB和Normal map合并为一张图,左边RGB,右边Normal map
        combined_image = np.concatenate((rgb, normal), axis=1)

        # 将合并后的图像加入到帧列表
        frames.append(combined_image)

    # 使用imageio保存视频
    imageio.mimsave(output_video_path, frames, fps=fps)

    print(f"Video saved to {output_video_path}")

if __name__ == '__main__':
    # 示例调用
    input_obj_path = "/hpc2hdd/home/jlin695/code/github/Kiss3DGen/outputs/a_owl_wearing_a_hat/ISOMER/rgb_projected.obj"
    output_video_path = "output.mp4"
    render_video_from_obj(input_obj_path, output_video_path)