Note
Download notebook and run it offline.
Tutorial03 - Human NeRF
Overview
🚀 This tutorial provides an example of rendering animated skeletal meshes from different points of view. The rendered images can support various research topics, including human pose and shape estimation (HPS) and novel view synthesis for human (Human NeRF). By the end of this tutorial, you will be able to:
Initialize XRFeitoria
Import a skeletal mesh with animation
Import another skeletal mesh without animation and setup animation for it
Set
Actor’s location in theLevelCreate a
Sequencefor renderingAdd multiple static cameras in the
SequenceAdd a moving camera with transform keys in the
SequenceRender images and annotations
1. Initialization
Then, similar to Tutorial01, specify your engine path and initialize XRFeitoria.
[ ]:
import xrfeitoria as xf
[ ]:
# Replace with your executable path
engine_exec_path = 'C:/Program Files/Blender Foundation/Blender 3.3/blender.exe'
# engine_exec_path = 'C:/Program Files/Epic Games/UE_5.2/Engine/Binaries/Win64/UnrealEditor-Cmd.exe'
[ ]:
from pathlib import Path
exec_path_stem = Path(engine_exec_path).stem.lower()
if 'blender' in exec_path_stem:
# Open Blender
render_engine = 'blender'
xf_runner = xf.init_blender(exec_path=engine_exec_path, background=False, new_process=True)
elif 'unreal' in exec_path_stem:
# Unreal Engine requires a project to be opened
# Here we use a sample project, which is downloaded from the following link
# You can also use your own project
import shutil
from xrfeitoria.utils.downloader import download
unreal_project_zip = download(url='https://openxrlab-share.oss-cn-hongkong.aliyuncs.com/xrfeitoria/tutorials/unreal_project/UE_Sample.zip',
dst_dir="./tutorial03/assets/")
shutil.unpack_archive(filename=unreal_project_zip, extract_dir='./tutorial03/assets/')
# Open Unreal Engine
render_engine = 'unreal'
xf_runner = xf.init_unreal(exec_path=engine_exec_path,
background=False,
new_process=True,
project_path='./tutorial03/assets/UE_sample/UE_sample.uproject')
✨ Now you can see a new Blender/Unreal Engine process has started.
2. Import skeletal meshes to Level
Download the skeletal meshes in SynBody to local folder and import them to Level.
[ ]:
from xrfeitoria.utils.downloader import download
# Download the skeletal meshes
actor1_path = download('https://openxrlab-share.oss-cn-hongkong.aliyuncs.com/xrfeitoria/tutorials/assets/SMPL-XL/SMPL-XL-00439__Subject_75_F_12.fbx', dst_dir="./tutorial03/assets/")
actor2_path = download('https://openxrlab-share.oss-cn-hongkong.aliyuncs.com/xrfeitoria/tutorials/assets/SMPL-XL/SMPL-XL-00045.fbx', dst_dir="./tutorial03/assets/")
actor2_motion_path = download('https://openxrlab-share.oss-cn-hongkong.aliyuncs.com/xrfeitoria/tutorials/assets/SMPL-XL/walking__15_01.fbx', dst_dir="./tutorial03/assets/")
Here we import two actors. The actor1 has animation and the actor2 has no animation.
And we set different stencil value for each Actor to distinguish different Actors when rendering segmentation masks.
[ ]:
# Import the skeletal mesh
actor1 = xf_runner.Actor.import_from_file(file_path=actor1_path, stencil_value=100)
actor2 = xf_runner.Actor.import_from_file(file_path=actor2_path, stencil_value=200)
Then, we load an animation from another file and set it to the actor2
[ ]:
actor2.setup_animation(animation_path=actor2_motion_path)
We can also modify the properties of the actors, such as the location, rotation, and scale.
[ ]:
# Set the location of the two actors to make their distance to be 1.0 meter
actor1_location = actor1.location
actor2_location = actor2.location
actor2.location = (actor1_location[0] + 1.0, actor1_location[1], actor1_location[2])
Now they look like:
Blender

Unreal Engine

If you use Unreal Engine, the Level should be saved after been modified.
[ ]:
# save the level
if render_engine == 'unreal':
xf_runner.utils.save_current_level()
3. Add a sequence for rendering
Sequence is a multifunctional class in XRFeitoria. It can be used for: - rendering - adding transform keys - grouping different objects.
Here, we use it for rendering.
[ ]:
import math
from xrfeitoria.data_structure.models import RenderPass
from xrfeitoria.data_structure.models import SequenceTransformKey as SeqTransKey
# Use `with` statement to create a sequence, and it will be automatically close the sequence after the code block is executed.
# The argument `seq_length` controls the number of frames to be rendered.
sequence_name = 'MySequence'
frame_num = 6
with xf_runner.Sequence.new(seq_name=sequence_name, seq_length=frame_num, replace=True) as seq:
# Get the bounding boxes of the actors
actor1_bbox = actor1.bound_box
actor2_bbox = actor2.bound_box
# Get the center location of the actors
actor1_center = ((actor1_bbox[0][0] + actor1_bbox[1][0]) / 2, (actor1_bbox[0][1] + actor1_bbox[1][1]) / 2, (actor1_bbox[0][2] + actor1_bbox[1][2]) / 2)
actor2_center = ((actor2_bbox[0][0] + actor2_bbox[1][0]) / 2, (actor2_bbox[0][1] + actor2_bbox[1][1]) / 2, (actor2_bbox[0][2] + actor2_bbox[1][2]) / 2)
actors_center = ((actor1_center[0] + actor2_center[0]) / 2, (actor1_center[1] + actor2_center[1]) / 2, (actor1_center[2] + actor2_center[2]) / 2)
##########################################################################
# Add 6 static cameras and a moving camera around the actors for rendering
##########################################################################
# Set cameras' field of view to 90°
camera_fov = 90
# Set cameras' distance to 3.0m
distance_to_actor = 3.0
# Prepare the transform keys for moving camera
transform_keys = []
# calculate the location and rotation of the cameras
for i in range(6):
azimuth = 360 / 6 * i
azimuth_radians = math.radians(azimuth)
x = distance_to_actor * math.cos(azimuth_radians) + actors_center[0]
y = distance_to_actor * math.sin(azimuth_radians) + actors_center[1]
z = 0.0 + actors_center[2]
location = (x, y, z)
# Set camera's rotation to look at the actor's center
rotation = xf_runner.utils.get_rotation_to_look_at(location=location, target=actors_center)
# Add a static camera
static_camera = seq.spawn_camera(
camera_name=f'static_camera_{i}',
location=location,
rotation=rotation,
fov=camera_fov,
)
# Add a transform key to the moving camera
transform_keys.append(
SeqTransKey(
frame=i,
location=location,
rotation=rotation,
interpolation='AUTO',
)
)
# Add a moving camera rotating around the actors
moving_camera = seq.spawn_camera_with_keys(
camera_name=f'moving_camera',
transform_keys=transform_keys,
fov=camera_fov,
)
# Add a render job to renderer
# In render job, you can specify the output path, resolution, render passes, etc.
# The output path is the path to save the rendered data.
# The resolution is the resolution of the rendered image.
# The render passes define what kind of data you want to render, such as img, depth, normal, etc.
# and what kind of format you want to save, such as png, exr, etc.
seq.add_to_renderer(
output_path=f'./tutorial03/outputs/{render_engine}/',
resolution=(1280, 720),
render_passes=[RenderPass('img', 'png'),
RenderPass('mask', 'exr'),
RenderPass('normal', 'exr'),
RenderPass('diffuse', 'exr')]
)
4. Render
The following code renders all the render jobs and save the images to the output_path set in seq.add_to_renderer above.
[ ]:
# Render
xf_runner.render()
Check the output_path, and you can see that for the frame i, the image rendered by the moving camera is the same as the image rendered by the ith static camera. For example, the moving_camera/0002.png is the same as the static_camera_2/0002.png.
[ ]:
import matplotlib.pyplot as plt
from xrfeitoria.utils.viewer import Viewer
xf_viewer = Viewer(sequence_dir=f'./tutorial03/outputs/{render_engine}/{sequence_name}/')
moving_camera_img = xf_viewer.get_img(camera_name='moving_camera', frame=2)
static_camera_img = xf_viewer.get_img(camera_name='static_camera_2', frame=2)
plt.figure(figsize=(20, 20))
plt.subplot(1, 2, 1)
plt.imshow(moving_camera_img)
plt.axis('off')
plt.title('moving_camera/0002.png')
plt.subplot(1, 2, 2)
plt.imshow(static_camera_img)
plt.axis('off')
plt.title('static_camera_2/0002.png')
View the rendered images and annotations of the camera static_camera_2 by:
[ ]:
import matplotlib.pyplot as plt
from xrfeitoria.utils.viewer import Viewer
xf_viewer = Viewer(sequence_dir=f'./tutorial03/outputs/{render_engine}/{sequence_name}/')
camera_name = 'static_camera_2'
for i in range(frame_num):
img = xf_viewer.get_img(camera_name=camera_name, frame=i)
mask = xf_viewer.get_mask(camera_name=camera_name, frame=i)
normal = xf_viewer.get_normal(camera_name=camera_name, frame=i)
diffuse = xf_viewer.get_diffuse(camera_name=camera_name, frame=i)
plt.figure(figsize=(20, 20))
plt.subplot(1, 4, 1)
plt.imshow(img)
plt.axis('off')
plt.title('img')
plt.subplot(1, 4, 2)
plt.imshow(mask)
plt.axis('off')
plt.title('mask')
plt.subplot(1, 4, 3)
plt.imshow(normal)
plt.axis('off')
plt.title('normal')
plt.subplot(1, 4, 4)
plt.imshow(diffuse)
plt.axis('off')
plt.title('diffuse')
Hint: When using Unreal Engine, if the images of the mask look weird, try running the notebook again.
Finally, close the engine by:
[ ]:
xf_runner.close()
Ref to api docs, you can always use with statement to ensure the engine is closed when the codes are finished.