Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ apps/apple/VulkanSplatting/ThirdParty
# xcuserdata under any directory is ignored
xcuserdata/

project.xcconfig
project.xcconfig
/build
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ if (WIN32 OR APPLE)
endif()

set(GLM_ENABLE_CXX_20 ON CACHE INTERNAL "Enable experimental features")
set(GLM_DISABLE_AUTO_DETECTION ON CACHE INTERNAL "Disable auto detection to avoid poison-system-directories warning")

FetchContent_Declare(
glm
Expand Down Expand Up @@ -65,6 +66,12 @@ FetchContent_Declare(imgui
)
FetchContent_MakeAvailable(imgui)

FetchContent_Declare(stb
GIT_REPOSITORY https://github.com/nothings/stb.git
GIT_TAG 5736b15f7ea0ffb08dd38af21067c314d6a3aae9
)
FetchContent_MakeAvailable(stb)

if (APPLE)
add_compile_definitions(__APPLE__)

Expand Down
56 changes: 51 additions & 5 deletions apps/viewer/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ int main(int argc, char** argv) {
args::ValueFlag<uint32_t> widthFlag{parser, "width", "Set window width", {'w', "width"}};
args::ValueFlag<uint32_t> heightFlag{parser, "height", "Set window height", {'h', "height"}};
args::Flag noGuiFlag{parser, "no-gui", "Disable GUI", { "no-gui"}};
args::ValueFlag<std::string> outputFlag{parser, "output", "Output image path for headless rendering", {'o', "output"}};
args::ValueFlagList<std::string> cameraFlag{parser, "camera", "Camera configuration file path(s)", {'c', "camera"}};
args::Positional<std::string> scenePath{parser, "scene", "Path to scene file", "scene.ply"};

try {
Expand Down Expand Up @@ -65,7 +67,7 @@ int main(int argc, char** argv) {
// check that the scene file exists
if (!std::filesystem::exists(config.scene)) {
spdlog::critical("File does not exist: {}", config.scene);
return 0;
return 1;
}

if (validationLayersFlag) {
Expand All @@ -86,21 +88,65 @@ int main(int argc, char** argv) {
config.enableGui = true;
}

if (outputFlag) {
config.outputPath = args::get(outputFlag);
config.enableGui = false;
}

// Handle multiple camera configurations
std::vector<std::string> cameraPaths;
if (cameraFlag) {
cameraPaths = args::get(cameraFlag);

// Multiple cameras only make sense with output path
if (cameraPaths.size() > 1 && !outputFlag) {
spdlog::critical("Multiple camera configurations can only be used with output path (-o)");
return 1;
}

// For single camera, use the existing logic
if (cameraPaths.size() == 1) {
config.cameraPath = cameraPaths[0];
}
}

auto width = widthFlag ? args::get(widthFlag) : 1280;
auto height = heightFlag ? args::get(heightFlag) : 720;

config.window = VulkanSplatting::createGlfwWindow("Vulkan Splatting", width, height);
if (outputFlag) {
// For headless mode, we still need a window but make it invisible
config.window = VulkanSplatting::createGlfwWindow("Vulkan Splatting (Headless)", width, height);
} else {
config.window = VulkanSplatting::createGlfwWindow("Vulkan Splatting", width, height);
}

#ifndef DEBUG
try {
#endif
auto renderer = VulkanSplatting(config);
renderer.start();
// Handle multiple camera configurations
if (cameraPaths.size() > 1) {
spdlog::info("Rendering with {} camera configurations", cameraPaths.size());

for (size_t i = 0; i < cameraPaths.size(); ++i) {
spdlog::info("Rendering with camera configuration {}: {}", i + 1, cameraPaths[i]);

// Create a new config for this camera
auto cameraConfig = config;
cameraConfig.cameraPath = cameraPaths[i];

auto renderer = VulkanSplatting(cameraConfig);
renderer.start();
}
} else {
// Single camera or no camera - use existing logic
auto renderer = VulkanSplatting(config);
renderer.start();
}
#ifndef DEBUG
} catch (const std::exception& e) {
spdlog::critical(e.what());
std::cout << e.what() << std::endl;
return 0;
return 1;
}
#endif
return 0;
Expand Down
15 changes: 15 additions & 0 deletions camera.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Camera Configuration File
# Format: key value1 value2 value3 ...

# Camera position (x, y, z)
position 0.0 2.0 5.0

# Camera rotation using Euler angles in degrees (pitch, yaw, roll)
euler -15.0 0.0 0.0

# Field of view in degrees
fov 60.0

# Near and far clipping planes
near 0.1
far 100.0
18 changes: 18 additions & 0 deletions camera_lookat.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Camera Configuration File with Look-At
# Alternative format using target and up vector

# Camera position
position 3.0 3.0 3.0

# Look at target point
target 0.0 0.0 0.0

# Up vector (optional, defaults to 0,1,0)
up 0.0 1.0 0.0

# Field of view
fov 45.0

# Clipping planes
near 0.2
far 1000.0
2 changes: 2 additions & 0 deletions include/3dgs/3dgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class VulkanSplatting {
float near = 0.2f;
float far = 1000.0f;
bool enableGui = false;
std::optional<std::string> outputPath = std::nullopt;
std::optional<std::string> cameraPath = std::nullopt;

std::shared_ptr<Window> window;
};
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ target_include_directories(3dgs_cpp
${imgui_SOURCE_DIR}/backends
${spdlog_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/shaders
${stb_SOURCE_DIR}
)

add_dependencies(3dgs_cpp shaders)
Expand Down
134 changes: 134 additions & 0 deletions src/CameraConfig.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#include "CameraConfig.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include <stdexcept>
#include <glm/gtc/matrix_transform.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/euler_angles.hpp>
#include <spdlog/spdlog.h>

CameraConfig CameraConfig::loadFromFile(const std::string& filePath) {
std::ifstream file(filePath);
if (!file.is_open()) {
throw std::runtime_error("Could not open camera file: " + filePath);
}

CameraConfig config;
std::string line;

while (std::getline(file, line)) {
// Skip empty lines and comments
if (line.empty() || line[0] == '#' || line[0] == '/') {
continue;
}

std::istringstream iss(line);
std::string key;
if (!(iss >> key)) {
continue;
}

if (key == "position") {
iss >> config.position.x >> config.position.y >> config.position.z;
spdlog::debug("Camera position: ({}, {}, {})",
config.position.x, config.position.y, config.position.z);
}
else if (key == "rotation") {
// Quaternion format: w x y z
iss >> config.rotation.w >> config.rotation.x >> config.rotation.y >> config.rotation.z;
spdlog::debug("Camera rotation (quat): ({}, {}, {}, {})",
config.rotation.w, config.rotation.x, config.rotation.y, config.rotation.z);
}
else if (key == "euler") {
// Euler angles in degrees: pitch yaw roll
glm::vec3 euler;
iss >> euler.x >> euler.y >> euler.z;
config.eulerAngles = euler;
spdlog::debug("Camera euler angles: ({}, {}, {})", euler.x, euler.y, euler.z);
}
else if (key == "target") {
glm::vec3 target;
iss >> target.x >> target.y >> target.z;
config.target = target;
spdlog::debug("Camera target: ({}, {}, {})", target.x, target.y, target.z);
}
else if (key == "up") {
glm::vec3 up;
iss >> up.x >> up.y >> up.z;
config.up = up;
spdlog::debug("Camera up: ({}, {}, {})", up.x, up.y, up.z);
}
else if (key == "fov") {
iss >> config.fov;
spdlog::debug("Camera FOV: {}", config.fov);
}
else if (key == "near") {
iss >> config.nearPlane;
spdlog::debug("Camera near plane: {}", config.nearPlane);
}
else if (key == "far") {
iss >> config.farPlane;
spdlog::debug("Camera far plane: {}", config.farPlane);
}
else {
spdlog::warn("Unknown camera config key: {}", key);
}
}

// Compute rotation if specified via alternative methods
if (config.eulerAngles.has_value()) {
config.computeRotationFromEuler();
} else if (config.target.has_value()) {
config.computeRotationFromLookAt();
}

return config;
}

bool CameraConfig::saveToFile(const CameraConfig& config, const std::string& filePath) {
std::ofstream file(filePath);
if (!file.is_open()) {
return false;
}

file << "# Camera Configuration File\n";
file << "# Format: key value1 value2 value3 ...\n\n";

file << "position " << config.position.x << " " << config.position.y << " " << config.position.z << "\n";
file << "rotation " << config.rotation.w << " " << config.rotation.x << " "
<< config.rotation.y << " " << config.rotation.z << "\n";
file << "fov " << config.fov << "\n";
file << "near " << config.nearPlane << "\n";
file << "far " << config.farPlane << "\n";

return true;
}

void CameraConfig::computeRotationFromEuler() {
if (!eulerAngles.has_value()) return;

// Convert degrees to radians
glm::vec3 radians = glm::radians(eulerAngles.value());

// Create rotation from Euler angles (pitch, yaw, roll)
rotation = glm::quat(radians);

spdlog::debug("Computed rotation from Euler: ({}, {}, {}, {})",
rotation.w, rotation.x, rotation.y, rotation.z);
}

void CameraConfig::computeRotationFromLookAt() {
if (!target.has_value()) return;

glm::vec3 upVec = up.value_or(glm::vec3(0.0f, 1.0f, 0.0f));

// Create look-at matrix
glm::mat4 lookAt = glm::lookAt(position, target.value(), upVec);

// Extract rotation quaternion from look-at matrix
rotation = glm::quat_cast(lookAt);

spdlog::debug("Computed rotation from look-at: ({}, {}, {}, {})",
rotation.w, rotation.x, rotation.y, rotation.z);
}
31 changes: 31 additions & 0 deletions src/CameraConfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef CAMERACONFIG_H
#define CAMERACONFIG_H

#include <string>
#include <optional>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>

struct CameraConfig {
glm::vec3 position{0.0f, 0.0f, 0.0f};
glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; // Identity quaternion
float fov = 45.0f;
float nearPlane = 0.1f;
float farPlane = 1000.0f;

// Alternative: specify rotation as Euler angles (degrees)
std::optional<glm::vec3> eulerAngles = std::nullopt;

// Alternative: specify look-at target
std::optional<glm::vec3> target = std::nullopt;
std::optional<glm::vec3> up = std::nullopt;

static CameraConfig loadFromFile(const std::string& filePath);
static bool saveToFile(const CameraConfig& config, const std::string& filePath);

private:
void computeRotationFromEuler();
void computeRotationFromLookAt();
};

#endif // CAMERACONFIG_H
Loading
Loading