From 25dec33c253f035485bb2e1f8563e12ef3134e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <thomas94@gmx.net> Date: Wed, 31 Aug 2022 14:32:45 +0200 Subject: [PATCH] Add option to loop camera paths Courtesy of Eric Haines --- .../neural-graphics-primitives/camera_path.h | 19 ++++++++++++++++--- include/neural-graphics-primitives/testbed.h | 2 ++ scripts/run.py | 2 ++ src/camera_path.cu | 2 +- src/python_api.cu | 1 + src/testbed.cu | 8 ++++++++ 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/include/neural-graphics-primitives/camera_path.h b/include/neural-graphics-primitives/camera_path.h index b520b69..16a33e7 100644 --- a/include/neural-graphics-primitives/camera_path.h +++ b/include/neural-graphics-primitives/camera_path.h @@ -72,12 +72,25 @@ struct CameraPath { bool m_update_cam_from_path = false; float m_playtime = 0.f; float m_autoplayspeed = 0.f; - - const CameraKeyframe& get_keyframe(int i) { return m_keyframes[tcnn::clamp(i, 0, (int)m_keyframes.size()-1)]; } + // If m_loop is set true, the last frame set will be more like "next to last," + // with animation then returning back to the first frame, making a continuous loop. + // Note that the user does not have to (and should not normally) duplicate the first frame to be the last frame. + bool m_loop = false; + + const CameraKeyframe& get_keyframe(int i) { + if (m_loop) { + int size = (int)m_keyframes.size(); + // add size to ensure no negative value is generated by modulo + return m_keyframes[(i + size) % size]; + } else { + return m_keyframes[tcnn::clamp(i, 0, (int)m_keyframes.size()-1)]; + } + } CameraKeyframe eval_camera_path(float t) { if (m_keyframes.empty()) return {}; - t *= (float)(m_keyframes.size()-1); + // make room for last frame == first frame when looping + t *= (float)(m_loop ? m_keyframes.size() : m_keyframes.size()-1); int t1 = (int)floorf(t); return spline(t-floorf(t), get_keyframe(t1-1), get_keyframe(t1), get_keyframe(t1+1), get_keyframe(t1+2)); } diff --git a/include/neural-graphics-primitives/testbed.h b/include/neural-graphics-primitives/testbed.h index 521e8a3..227ffec 100644 --- a/include/neural-graphics-primitives/testbed.h +++ b/include/neural-graphics-primitives/testbed.h @@ -388,6 +388,8 @@ public: void set_camera_from_time(float t); void update_loss_graph(); void load_camera_path(const std::string& filepath_string); + bool loop_animation(); + void set_loop_animation(bool value); float compute_image_mse(bool quantize_to_byte); diff --git a/scripts/run.py b/scripts/run.py index aed15f8..151e924 100644 --- a/scripts/run.py +++ b/scripts/run.py @@ -46,6 +46,7 @@ def parse_args(): parser.add_argument("--video_camera_path", default="", help="The camera path to render, e.g., base_cam.json.") parser.add_argument("--video_camera_smoothing", action="store_true", help="Applies additional smoothing to the camera trajectory with the caveat that the endpoint of the camera path may not be reached.") + parser.add_argument("--video_loop_animation", action="store_true", help="Connect the last and first keyframes in a continuous loop.") parser.add_argument("--video_fps", type=int, default=60, help="Number of frames per second.") parser.add_argument("--video_n_seconds", type=int, default=1, help="Number of seconds the rendered video should be long.") parser.add_argument("--video_spp", type=int, default=8, help="Number of samples per pixel. A larger number means less noise, but slower rendering.") @@ -336,6 +337,7 @@ if __name__ == "__main__": if args.video_camera_path: testbed.load_camera_path(args.video_camera_path) + testbed.loop_animation = args.video_loop_animation resolution = [args.width or 1920, args.height or 1080] n_frames = args.video_n_seconds * args.video_fps diff --git a/src/camera_path.cu b/src/camera_path.cu index 88d8be0..2815448 100644 --- a/src/camera_path.cu +++ b/src/camera_path.cu @@ -60,7 +60,7 @@ CameraKeyframe spline(float t, const CameraKeyframe& p0, const CameraKeyframe& p CameraKeyframe r1 = lerp(q1, q2, t, 0.f, 2.f); return lerp(r0, r1, t, 0.f, 1.f); } else { - // cublic bspline + // cubic bspline float tt=t*t; float ttt=t*t*t; float a = (1-t)*(1-t)*(1-t)*(1.f/6.f); diff --git a/src/python_api.cu b/src/python_api.cu index 6d4427d..ef41fa6 100644 --- a/src/python_api.cu +++ b/src/python_api.cu @@ -386,6 +386,7 @@ PYBIND11_MODULE(pyngp, m) { .def("save_snapshot", &Testbed::save_snapshot, py::arg("path"), py::arg("include_optimizer_state")=false, "Save a snapshot of the currently trained model") .def("load_snapshot", &Testbed::load_snapshot, py::arg("path"), "Load a previously saved snapshot") .def("load_camera_path", &Testbed::load_camera_path, "Load a camera path", py::arg("path")) + .def_property("loop_animation", &Testbed::loop_animation, &Testbed::set_loop_animation) .def("compute_and_save_png_slices", &Testbed::compute_and_save_png_slices, py::arg("filename"), py::arg("resolution") = Eigen::Vector3i::Constant(256), diff --git a/src/testbed.cu b/src/testbed.cu index 8052e50..78c1ac2 100644 --- a/src/testbed.cu +++ b/src/testbed.cu @@ -3008,5 +3008,13 @@ void Testbed::load_camera_path(const std::string& filepath_string) { m_camera_path.load(filepath_string, Matrix<float, 3, 4>::Identity()); } +bool Testbed::loop_animation() { + return m_camera_path.m_loop; +} + +void Testbed::set_loop_animation(bool value) { + m_camera_path.m_loop = value; +} + NGP_NAMESPACE_END -- GitLab