diff --git a/src/main.cpp b/src/main.cpp index f2fe199..7424ee8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,32 +2,63 @@ #include "cpu/CPURenderer.h" #include "metal/MetalRenderer.h" #include "util/ModelLoader.h" +#include "util/CLI.h" #include -int main() +int main(int argc, const char* argv[]) { + // Parse command-line arguments + CLIArgs cliArgs = parseCLIArgs(argc, argv); + + // Show help if requested + if (cliArgs.help) + { + printUsage(argv[0]); + return 0; + } + + // Create renderer std::unique_ptr renderer = std::make_unique(); - renderer->addDirectionalLight(DirectionalLight{ - .direction = glm::normalize(glm::vec3(-0.4f, -0.3f, -0.2f)), - .color = glm::vec3(1, 1, 1), - }); - renderer->addPointLight(PointLight{}); + + // Add lights from CLI arguments, or use defaults if none specified + if (cliArgs.pointLights.empty() && cliArgs.directionalLights.empty()) + { + // Use default lights when no CLI lights are specified + renderer->addDirectionalLight(DirectionalLight{ + .direction = glm::normalize(glm::vec3(-0.4f, -0.3f, -0.2f)), + .color = glm::vec3(1, 1, 1), + }); + renderer->addPointLight(PointLight{}); + } + else + { + // Apply lights from CLI arguments + applyCLIToScene(*renderer, cliArgs); + } + renderer->addModels(ModelLoader::loadModel("../res/models/stanford-bunny.obj"), glm::mat4(glm::vec4(1.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f))); renderer->generate(); + + // Initialize camera and render parameters with defaults Camera camera = Camera{ .position = glm::vec3(2, 1, 2), .target = glm::vec3(0, 0, 0), - .f = 0, - .A = 0, - .S_O = 6, + .f = 0.7f, + .A = 0.35f, + .S_O = 20.0f, }; RenderParameter render = RenderParameter{ .width = 1920, .height = 1080, .numSamples = 10000, }; + + // Apply CLI arguments (overrides defaults for specified values) + applyCLIToCamera(camera, cliArgs); + applyCLIToRenderParams(render, cliArgs); + renderer->startRender(camera, render); while (true) @@ -39,6 +70,11 @@ int main() ImGui::InputFloat("Focal Length", &camera.f); ImGui::InputFloat("Aperture", &camera.A); ImGui::InputFloat("S_O", &camera.S_O); + ImGui::InputFloat2("Sensor Size", &camera.sensorSize.x); + ImGui::Text("Material Parameters"); + ImGui::InputFloat3("Fog Emissive", &camera.fogEmm.x); + ImGui::InputFloat("Specular Coeff", &camera.ks); + ImGui::InputFloat("Ambient Coeff", &camera.ka); ImGui::Text("Render Parameters"); ImGui::InputInt2("Dimensions", (int*)&render.width); ImGui::InputInt("Samples", (int*)&render.numSamples); diff --git a/src/metal/MetalRenderer.mm b/src/metal/MetalRenderer.mm index bee58e5..e9d5977 100644 --- a/src/metal/MetalRenderer.mm +++ b/src/metal/MetalRenderer.mm @@ -195,10 +195,10 @@ void MetalRenderer::render(Camera camera, RenderParameter parameter) .f = camera.f, .cameraForward = camera.target - camera.position, .S_O = camera.S_O, - .fogEmm = glm::vec3(0, 0, 0), - .ks = 0, + .fogEmm = camera.fogEmm, + .ks = camera.ks, .A = camera.A, - .ka = 0, + .ka = camera.ka, .sensorSize = camera.sensorSize, .width = parameter.width, .height = parameter.height, diff --git a/src/util/CLI.cpp b/src/util/CLI.cpp new file mode 100644 index 0000000..14900c7 --- /dev/null +++ b/src/util/CLI.cpp @@ -0,0 +1,403 @@ +#include "CLI.h" +#include "scene/Renderer.h" +#include +#include +#include + +void printUsage(const char* programName) +{ + std::cout << "Usage: " << programName << " [options]\n" + << "\n" + << "Camera Parameters:\n" + << " --pos Camera position (default: 2 1 2)\n" + << " --target Camera target (default: 0 0 0)\n" + << " --focal Focal length in meters (default: 0.7)\n" + << " --aperture Aperture radius in meters (default: 0.35)\n" + << " --subject-dist Subject distance S_O in meters (default: 20)\n" + << " --sensor Sensor size in meters (default: 0.036 0.024)\n" + << "\n" + << "Material Parameters:\n" + << " --fog Fog emissive color (default: 0 0 0)\n" + << " --specular Specular coefficient (default: 0)\n" + << " --ambient Ambient coefficient (default: 0)\n" + << "\n" + << "Light Parameters:\n" + << " --point-light [attenuation]\n" + << " Add a point light (default attenuation: 1.0)\n" + << " --dir-light \n" + << " Add a directional light\n" + << "\n" + << "Render Parameters:\n" + << " --width Image width in pixels (default: 1920)\n" + << " --height Image height in pixels (default: 1080)\n" + << " --samples Number of samples per pixel (default: 10000)\n" + << "\n" + << "Other:\n" + << " -h, --help Show this help message\n" + << "\n" + << "Examples:\n" + << " " << programName << " --width 1280 --height 720 --samples 1000\n" + << " " << programName << " --pos 3 2 3 --focal 0.5\n" + << " " << programName << " --fog 0.1 0.1 0.2 --specular 0.5 --ambient 0.1\n" + << " " << programName << " --point-light 0 5 0 1 1 1 --dir-light -1 -1 -1 1 0.8 0.6\n" + << std::endl; +} + +static bool parseFloat(const char* str, float& out) +{ + try + { + out = std::stof(str); + return true; + } + catch (...) + { + return false; + } +} + +static bool parseUint(const char* str, uint32_t& out) +{ + try + { + out = static_cast(std::stoul(str)); + return true; + } + catch (...) + { + return false; + } +} + +static bool parseVec3(const char* xStr, const char* yStr, const char* zStr, glm::vec3& out) +{ + float x, y, z; + if (!parseFloat(xStr, x) || !parseFloat(yStr, y) || !parseFloat(zStr, z)) + { + return false; + } + out = glm::vec3(x, y, z); + return true; +} + +static bool parseVec2(const char* xStr, const char* yStr, glm::vec2& out) +{ + float x, y; + if (!parseFloat(xStr, x) || !parseFloat(yStr, y)) + { + return false; + } + out = glm::vec2(x, y); + return true; +} + +CLIArgs parseCLIArgs(int argc, const char* argv[]) +{ + CLIArgs args; + + for (int i = 1; i < argc; ++i) + { + std::string arg = argv[i]; + + if (arg == "-h" || arg == "--help") + { + args.help = true; + continue; + } + + if (arg == "--pos" && i + 3 < argc) + { + glm::vec3 pos; + if (parseVec3(argv[i + 1], argv[i + 2], argv[i + 3], pos)) + { + args.cameraPosition = pos; + i += 3; + } + else + { + std::cerr << "Error: Invalid camera position values\n"; + std::exit(1); + } + continue; + } + + if (arg == "--target" && i + 3 < argc) + { + glm::vec3 target; + if (parseVec3(argv[i + 1], argv[i + 2], argv[i + 3], target)) + { + args.cameraTarget = target; + i += 3; + } + else + { + std::cerr << "Error: Invalid camera target values\n"; + std::exit(1); + } + continue; + } + + if (arg == "--focal" && i + 1 < argc) + { + float f; + if (parseFloat(argv[i + 1], f)) + { + args.focalLength = f; + ++i; + } + else + { + std::cerr << "Error: Invalid focal length value\n"; + std::exit(1); + } + continue; + } + + if (arg == "--aperture" && i + 1 < argc) + { + float A; + if (parseFloat(argv[i + 1], A)) + { + args.aperture = A; + ++i; + } + else + { + std::cerr << "Error: Invalid aperture value\n"; + std::exit(1); + } + continue; + } + + if (arg == "--subject-dist" && i + 1 < argc) + { + float d; + if (parseFloat(argv[i + 1], d)) + { + args.subjectDistance = d; + ++i; + } + else + { + std::cerr << "Error: Invalid subject distance value\n"; + std::exit(1); + } + continue; + } + + if (arg == "--sensor" && i + 2 < argc) + { + glm::vec2 sensor; + if (parseVec2(argv[i + 1], argv[i + 2], sensor)) + { + args.sensorSize = sensor; + i += 2; + } + else + { + std::cerr << "Error: Invalid sensor size values\n"; + std::exit(1); + } + continue; + } + + if (arg == "--fog" && i + 3 < argc) + { + glm::vec3 fog; + if (parseVec3(argv[i + 1], argv[i + 2], argv[i + 3], fog)) + { + args.fogEmissive = fog; + i += 3; + } + else + { + std::cerr << "Error: Invalid fog emissive values\n"; + std::exit(1); + } + continue; + } + + if (arg == "--specular" && i + 1 < argc) + { + float s; + if (parseFloat(argv[i + 1], s)) + { + args.specularCoeff = s; + ++i; + } + else + { + std::cerr << "Error: Invalid specular coefficient value\n"; + std::exit(1); + } + continue; + } + + if (arg == "--ambient" && i + 1 < argc) + { + float a; + if (parseFloat(argv[i + 1], a)) + { + args.ambientCoeff = a; + ++i; + } + else + { + std::cerr << "Error: Invalid ambient coefficient value\n"; + std::exit(1); + } + continue; + } + + if (arg == "--point-light" && i + 5 < argc) + { + glm::vec3 position, color; + if (parseVec3(argv[i + 1], argv[i + 2], argv[i + 3], position) && + parseVec3(argv[i + 4], argv[i + 5], argv[i + 6], color)) + { + PointLight light; + light.position = position; + light.color = color; + light.attenuation = 1.0f; + if (i + 7 < argc) + { + if (!parseFloat(argv[i + 7], light.attenuation)) + { + std::cerr << "Error: Invalid point light attenuation value\n"; + std::exit(1); + } + i += 7; + } + else + { + i += 6; + } + args.pointLights.push_back(light); + } + else + { + std::cerr << "Error: Invalid point light values (expected: [attenuation])\n"; + std::exit(1); + } + continue; + } + + if (arg == "--dir-light" && i + 5 < argc) + { + glm::vec3 direction, color; + if (parseVec3(argv[i + 1], argv[i + 2], argv[i + 3], direction) && + parseVec3(argv[i + 4], argv[i + 5], argv[i + 6], color)) + { + DirectionalLight light; + light.direction = direction; + light.color = color; + i += 6; + args.directionalLights.push_back(light); + } + else + { + std::cerr << "Error: Invalid directional light values (expected: )\n"; + std::exit(1); + } + continue; + } + + if (arg == "--width" && i + 1 < argc) + { + uint32_t w; + if (parseUint(argv[i + 1], w)) + { + args.width = w; + ++i; + } + else + { + std::cerr << "Error: Invalid width value\n"; + std::exit(1); + } + continue; + } + + if (arg == "--height" && i + 1 < argc) + { + uint32_t h; + if (parseUint(argv[i + 1], h)) + { + args.height = h; + ++i; + } + else + { + std::cerr << "Error: Invalid height value\n"; + std::exit(1); + } + continue; + } + + if (arg == "--samples" && i + 1 < argc) + { + uint32_t n; + if (parseUint(argv[i + 1], n)) + { + args.numSamples = n; + ++i; + } + else + { + std::cerr << "Error: Invalid samples value\n"; + std::exit(1); + } + continue; + } + + std::cerr << "Error: Unknown argument '" << arg << "'\n"; + std::cerr << "Use --help for usage information\n"; + std::exit(1); + } + + return args; +} + +void applyCLIToCamera(Camera& camera, const CLIArgs& args) +{ + if (args.cameraPosition) + camera.position = *args.cameraPosition; + if (args.cameraTarget) + camera.target = *args.cameraTarget; + if (args.focalLength) + camera.f = *args.focalLength; + if (args.aperture) + camera.A = *args.aperture; + if (args.subjectDistance) + camera.S_O = *args.subjectDistance; + if (args.sensorSize) + camera.sensorSize = *args.sensorSize; + if (args.fogEmissive) + camera.fogEmm = *args.fogEmissive; + if (args.specularCoeff) + camera.ks = *args.specularCoeff; + if (args.ambientCoeff) + camera.ka = *args.ambientCoeff; +} + +void applyCLIToRenderParams(RenderParameter& params, const CLIArgs& args) +{ + if (args.width) + params.width = *args.width; + if (args.height) + params.height = *args.height; + if (args.numSamples) + params.numSamples = *args.numSamples; +} + +void applyCLIToScene(Scene& scene, const CLIArgs& args) +{ + for (const auto& light : args.pointLights) + { + scene.addPointLight(light); + } + for (const auto& light : args.directionalLights) + { + scene.addDirectionalLight(light); + } +} diff --git a/src/util/CLI.h b/src/util/CLI.h new file mode 100644 index 0000000..21ea806 --- /dev/null +++ b/src/util/CLI.h @@ -0,0 +1,61 @@ +#pragma once +#include "Camera.h" +#include "Scene.h" +#include +#include +#include +#include + +// Forward declaration - defined in scene/Renderer.h +struct RenderParameter; + +/// Parsed command-line arguments for render parameters +struct CLIArgs +{ + // Camera parameters + std::optional cameraPosition; + std::optional cameraTarget; + std::optional focalLength; + std::optional aperture; + std::optional subjectDistance; + std::optional sensorSize; + std::optional fogEmissive; + std::optional specularCoeff; + std::optional ambientCoeff; + + // Light parameters + std::vector pointLights; + std::vector directionalLights; + + // Render parameters + std::optional width; + std::optional height; + std::optional numSamples; + + // Flags + bool help = false; +}; + +/// Parse command-line arguments +/// @param argc Argument count +/// @param argv Argument values +/// @return Parsed CLIArgs structure +CLIArgs parseCLIArgs(int argc, const char* argv[]); + +/// Apply CLI arguments to camera, using defaults for unspecified values +/// @param camera Camera to modify +/// @param args Parsed CLI arguments +void applyCLIToCamera(Camera& camera, const CLIArgs& args); + +/// Apply CLI arguments to render parameters, using defaults for unspecified values +/// @param params RenderParameter to modify +/// @param args Parsed CLI arguments +void applyCLIToRenderParams(RenderParameter& params, const CLIArgs& args); + +/// Apply CLI light arguments to scene +/// @param scene Scene to add lights to +/// @param args Parsed CLI arguments +void applyCLIToScene(Scene& scene, const CLIArgs& args); + +/// Print usage/help information +void printUsage(const char* programName); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 455f720..7b822a4 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -3,6 +3,8 @@ target_sources(RayTracer BRDF.h BRDF.cpp Camera.h + CLI.h + CLI.cpp Material.h Material.cpp Model.h @@ -14,4 +16,4 @@ target_sources(RayTracer Texture.cpp TextureLoader.h TextureLoader.cpp -) \ No newline at end of file +) diff --git a/src/util/Camera.h b/src/util/Camera.h index f5ced55..74f0c4d 100644 --- a/src/util/Camera.h +++ b/src/util/Camera.h @@ -9,4 +9,7 @@ struct Camera float S_O = 20; float f = 0.7; float A = 0.35; -}; \ No newline at end of file + glm::vec3 fogEmm = glm::vec3(0, 0, 0); + float ks = 0; + float ka = 0; +};