Skip to main content

modify/
modify.rs

1//! This example modifies the entire model using [`BasicModifier`](wgpu_3dgs_editor::BasicModifier).
2//!
3//! For example, to decrease the contrast of the model:
4//!
5//! ```sh
6//! cargo run --example modify -- -m "path/to/model.ply" --contrast "-1.0"
7//! ```
8
9use clap::Parser;
10use glam::*;
11
12use wgpu_3dgs_editor::{self as gs};
13
14/// The command line arguments.
15#[derive(Parser, Debug)]
16#[command(
17    version,
18    about,
19    long_about = "\
20    A 3D Gaussian splatting editor to apply basic modifier to all Gaussians in a model.
21    "
22)]
23struct Args {
24    /// Path to the .ply or .spz file.
25    #[arg(short, long, default_value = "examples/model.ply")]
26    model: String,
27
28    /// The output path for the modified .ply or .spz file.
29    #[arg(short, long, default_value = "target/output.ply")]
30    output: String,
31
32    /// Whether to override the RGB color of the selected Gaussians.
33    #[arg(long)]
34    override_rgb: bool,
35
36    /// If [`Args::override_rgb`], then it is used to override the RGB color,
37    /// otherwise it is used to apply HSV modifications.
38    ///
39    /// Normally hue (H) is in [0, 1], saturation (S) and value (V) are in [0, 2].
40    /// This function adds the hue and multiplies saturation and value.
41    #[arg(
42        long,
43        allow_hyphen_values = true,
44        num_args = 3,
45        value_delimiter = ',',
46        default_value = "0.0,1.0,1.0"
47    )]
48    rgb_or_hsv: Vec<f32>,
49
50    /// Alpha is multiplied with the original alpha.
51    #[arg(long, allow_hyphen_values = true, default_value = "1.0")]
52    alpha: f32,
53
54    /// Contrast is applied to the RGB color.
55    ///
56    /// Normally the range is [-1, 1].
57    #[arg(long, allow_hyphen_values = true, default_value = "0.0")]
58    contrast: f32,
59
60    /// Exposure is applied to the RGB color.
61    ///
62    /// Normally the range is [-5, 5].
63    #[arg(long, allow_hyphen_values = true, default_value = "0.0")]
64    exposure: f32,
65
66    /// Gamma is applied to the RGB color.
67    ///
68    /// Normally the range is [0, 5].
69    #[arg(long, allow_hyphen_values = true, default_value = "1.0")]
70    gamma: f32,
71}
72
73type GaussianPod = gs::core::GaussianPodWithShSingleCov3dRotScaleConfigs;
74
75#[pollster::main]
76async fn main() {
77    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
78
79    let args = Args::parse();
80    let model_path = &args.model;
81
82    log::debug!("Creating wgpu instance");
83    let instance =
84        wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
85
86    log::debug!("Requesting adapter");
87    let adapter = instance
88        .request_adapter(&wgpu::RequestAdapterOptions::default())
89        .await
90        .expect("adapter");
91
92    log::debug!("Requesting device");
93    let (device, queue) = adapter
94        .request_device(&wgpu::DeviceDescriptor {
95            label: Some("Device"),
96            required_limits: adapter.limits(),
97            ..Default::default()
98        })
99        .await
100        .expect("device");
101
102    log::debug!("Creating gaussians");
103    let gaussians = [
104        gs::core::GaussiansSource::Ply,
105        gs::core::GaussiansSource::Spz,
106    ]
107    .into_iter()
108    .find_map(|source| gs::core::Gaussians::read_from_file(model_path, source).ok())
109    .expect("gaussians");
110
111    log::debug!("Creating editor");
112    let editor = gs::Editor::<GaussianPod>::new(&device, &gaussians);
113
114    log::debug!("Creating basic modifier");
115    let basic_modifier = gs::BasicModifier::<GaussianPod>::new(
116        &device,
117        &editor.gaussians_buffer,
118        &editor.model_transform_buffer,
119        &editor.gaussian_transform_buffer,
120    );
121
122    log::debug!("Configuring modifiers");
123    basic_modifier.basic_color_modifiers_buffer.update(
124        &queue,
125        match args.override_rgb {
126            true => gs::BasicColorRgbOverrideOrHsvModifiersPod::new_rgb_override,
127            false => gs::BasicColorRgbOverrideOrHsvModifiersPod::new_hsv_modifiers,
128        }(Vec3::from_slice(&args.rgb_or_hsv)),
129        args.alpha,
130        args.contrast,
131        args.exposure,
132        args.gamma,
133    );
134
135    log::info!("Starting editing process");
136    let time = std::time::Instant::now();
137
138    log::debug!("Editing Gaussians");
139    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
140        label: Some("Edit Encoder"),
141    });
142
143    editor.apply(
144        &device,
145        &mut encoder,
146        [&basic_modifier as &dyn gs::Modifier<GaussianPod>],
147    );
148
149    queue.submit(Some(encoder.finish()));
150
151    device
152        .poll(wgpu::PollType::wait_indefinitely())
153        .expect("poll");
154
155    log::info!("Editing process completed in {:?}", time.elapsed());
156
157    log::debug!("Downloading Gaussians");
158    let modified_gaussians = editor
159        .gaussians_buffer
160        .download_gaussians(&device, &queue)
161        .await
162        .map(|gs| {
163            match &args.output[args.output.len().saturating_sub(4)..] {
164                ".ply" => {
165                    gs::core::Gaussians::Ply(gs::core::PlyGaussians::from_iter(gs.into_iter()))
166                }
167                ".spz" => {
168                    gs::core::Gaussians::Spz(
169                        gs::core::SpzGaussians::from_gaussians_with_options(
170                            gs,
171                            &gs::core::SpzGaussiansFromGaussianSliceOptions {
172                                version: 2, // Version 2 is more widely supported as of now
173                                ..Default::default()
174                            },
175                        )
176                        .expect("SpzGaussians from gaussians"),
177                    )
178                }
179                _ => panic!("Unsupported output file extension, expected .ply or .spz"),
180            }
181        })
182        .expect("gaussians download");
183
184    log::debug!("Writing modified Gaussians to output file");
185    modified_gaussians
186        .write_to_file(&args.output)
187        .expect("write modified Gaussians to output file");
188
189    log::info!("Modified Gaussians written to {}", args.output);
190}