Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc827ca44d |
@@ -18,7 +18,6 @@ find_package(glm CONFIG REQUIRED)
|
|||||||
find_package(Ktx CONFIG REQUIRED)
|
find_package(Ktx CONFIG REQUIRED)
|
||||||
find_package(imgui CONFIG REQUIRED)
|
find_package(imgui CONFIG REQUIRED)
|
||||||
find_package(slang CONFIG REQUIRED)
|
find_package(slang CONFIG REQUIRED)
|
||||||
find_package(CLI11 CONFIG REQUIRED)
|
|
||||||
|
|
||||||
add_executable(RayTracer "")
|
add_executable(RayTracer "")
|
||||||
target_include_directories(RayTracer PUBLIC src/)
|
target_include_directories(RayTracer PUBLIC src/)
|
||||||
@@ -29,7 +28,6 @@ target_link_libraries(RayTracer PUBLIC GLEW::GLEW)
|
|||||||
target_link_libraries(RayTracer PUBLIC glm::glm)
|
target_link_libraries(RayTracer PUBLIC glm::glm)
|
||||||
target_link_libraries(RayTracer PUBLIC KTX::ktx)
|
target_link_libraries(RayTracer PUBLIC KTX::ktx)
|
||||||
target_link_libraries(RayTracer PUBLIC slang::slang)
|
target_link_libraries(RayTracer PUBLIC slang::slang)
|
||||||
target_link_libraries(RayTracer PUBLIC CLI11::CLI11)
|
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
target_include_directories(RayTracer PUBLIC ${VCPKG_INSTALLED_DIR}/arm64-osx/include)
|
target_include_directories(RayTracer PUBLIC ${VCPKG_INSTALLED_DIR}/arm64-osx/include)
|
||||||
|
|||||||
+4
-4
@@ -8,7 +8,7 @@
|
|||||||
int main(int argc, const char* argv[])
|
int main(int argc, const char* argv[])
|
||||||
{
|
{
|
||||||
// Parse command-line arguments
|
// Parse command-line arguments
|
||||||
CLIOptions cliOpts = AppCLI::parseArgs(argc, argv);
|
CLIOptions cliOpts = CLI::parseArgs(argc, argv);
|
||||||
|
|
||||||
// If help was requested, just exit after printing
|
// If help was requested, just exit after printing
|
||||||
if (cliOpts.showHelp)
|
if (cliOpts.showHelp)
|
||||||
@@ -30,7 +30,7 @@ int main(int argc, const char* argv[])
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AppCLI::applyLights(renderer.get(), cliOpts);
|
CLI::applyLights(renderer.get(), cliOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->addModels(ModelLoader::loadModel("../res/models/stanford-bunny.obj"),
|
renderer->addModels(ModelLoader::loadModel("../res/models/stanford-bunny.obj"),
|
||||||
@@ -39,8 +39,8 @@ int main(int argc, const char* argv[])
|
|||||||
renderer->generate();
|
renderer->generate();
|
||||||
|
|
||||||
// Create camera and render parameters from CLI options
|
// Create camera and render parameters from CLI options
|
||||||
Camera camera = AppCLI::toCamera(cliOpts);
|
Camera camera = CLI::toCamera(cliOpts);
|
||||||
RenderParameter render = AppCLI::toRenderParameter(cliOpts);
|
RenderParameter render = CLI::toRenderParameter(cliOpts);
|
||||||
|
|
||||||
renderer->startRender(camera, render);
|
renderer->startRender(camera, render);
|
||||||
|
|
||||||
|
|||||||
+403
-121
@@ -1,9 +1,93 @@
|
|||||||
#include "CLI.h"
|
#include "CLI.h"
|
||||||
#include <CLI/CLI.hpp>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <fstream>
|
||||||
|
#include <numbers>
|
||||||
|
|
||||||
namespace AppCLI
|
namespace CLI
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
float parseFloat(const std::string& str, const std::string& argName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return std::stof(str);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Error: Invalid float value for " << argName << ": " << str << "\n";
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t parseUInt(const std::string& str, const std::string& argName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return static_cast<uint32_t>(std::stoul(str));
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Error: Invalid unsigned integer value for " << argName << ": " << str << "\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 parseVec3(const std::vector<std::string>& tokens, size_t offset, const std::string& argName)
|
||||||
|
{
|
||||||
|
if (offset + 3 > tokens.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: " << argName << " requires 3 float values (x y z)\n";
|
||||||
|
return glm::vec3(0, 0, 0);
|
||||||
|
}
|
||||||
|
return glm::vec3(
|
||||||
|
parseFloat(tokens[offset], argName + " x"),
|
||||||
|
parseFloat(tokens[offset + 1], argName + " y"),
|
||||||
|
parseFloat(tokens[offset + 2], argName + " z")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 parseVec2(const std::vector<std::string>& tokens, size_t offset, const std::string& argName)
|
||||||
|
{
|
||||||
|
if (offset + 2 > tokens.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: " << argName << " requires 2 float values\n";
|
||||||
|
return glm::vec2(0, 0);
|
||||||
|
}
|
||||||
|
return glm::vec2(
|
||||||
|
parseFloat(tokens[offset], argName + " x"),
|
||||||
|
parseFloat(tokens[offset + 1], argName + " y")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RendererType parseRendererType(const std::string& str)
|
||||||
|
{
|
||||||
|
if (str == "cpu" || str == "CPU")
|
||||||
|
return RendererType::CPU;
|
||||||
|
if (str == "gpu" || str == "GPU" || str == "vulkan" || str == "Vulkan")
|
||||||
|
return RendererType::GPU;
|
||||||
|
if (str == "metal" || str == "Metal")
|
||||||
|
return RendererType::Metal;
|
||||||
|
|
||||||
|
std::cerr << "Warning: Unknown renderer type '" << str << "', using CPU renderer\n";
|
||||||
|
return RendererType::CPU;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* rendererTypeToString(RendererType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case RendererType::CPU: return "CPU";
|
||||||
|
case RendererType::GPU: return "GPU (Vulkan)";
|
||||||
|
case RendererType::Metal: return "Metal";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void printHelp()
|
void printHelp()
|
||||||
{
|
{
|
||||||
std::cout << R"(
|
std::cout << R"(
|
||||||
@@ -11,7 +95,7 @@ Usage: RayTracer [options]
|
|||||||
|
|
||||||
Render Parameters:
|
Render Parameters:
|
||||||
-w, --width <int> Image width (default: 1920)
|
-w, --width <int> Image width (default: 1920)
|
||||||
--height <int> Image height (default: 1080)
|
-h, --height <int> Image height (default: 1080)
|
||||||
-s, --samples <int> Number of samples per pixel (default: 10000)
|
-s, --samples <int> Number of samples per pixel (default: 10000)
|
||||||
-b, --bounces <int> Maximum ray bounces (default: 4)
|
-b, --bounces <int> Maximum ray bounces (default: 4)
|
||||||
--bg-color <r> <g> <b> Background/sky color (default: 0.05 0.05 0.1)
|
--bg-color <r> <g> <b> Background/sky color (default: 0.05 0.05 0.1)
|
||||||
@@ -37,151 +121,286 @@ Light Parameters:
|
|||||||
--point-light <x> <y> <z> <r> <g> <b> <att>
|
--point-light <x> <y> <z> <r> <g> <b> <att>
|
||||||
Add point light (position + color + attenuation)
|
Add point light (position + color + attenuation)
|
||||||
|
|
||||||
|
Model Parameters:
|
||||||
|
--model <file> Model file path (OBJ, FBX, GLB, etc.)
|
||||||
|
--model-scale <x> <y> <z> Model scale (default: 1 1 1)
|
||||||
|
--model-translate <x> <y> <z>
|
||||||
|
Model translation (default: 0 0 0)
|
||||||
|
--model-rotate <x> <y> <z> Model rotation in degrees (default: 0 0 0)
|
||||||
|
|
||||||
|
Renderer Selection:
|
||||||
|
--renderer <type> Renderer type: cpu, gpu, metal (default: cpu)
|
||||||
|
|
||||||
Other:
|
Other:
|
||||||
-h, --help Show this help message
|
--headless Run without GUI (render and save to file)
|
||||||
|
--help Show this help message
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
RayTracer -w 1280 --height 720 -s 1000
|
RayTracer -w 1280 -h 720 -s 1000
|
||||||
RayTracer --cam-pos 3 2 3 --cam-focal 0.5
|
RayTracer --cam-pos 3 2 3 --cam-focal 0.5
|
||||||
RayTracer --dir-light -0.4 -0.3 -0.2 1 1 1
|
RayTracer --dir-light -0.4 -0.3 -0.2 1 1 1
|
||||||
RayTracer --point-light 0 5 0 1 0.5 0 0.5
|
RayTracer --point-light 0 5 0 1 0.5 0 0.5
|
||||||
RayTracer --fog-emission 0.1 0.2 0.3 --specular-coeff 0.5 --ambient-coeff 0.2
|
RayTracer --fog-emission 0.1 0.2 0.3 --specular-coeff 0.5 --ambient-coeff 0.2
|
||||||
RayTracer -b 8 --bg-color 0.1 0.1 0.2 -o output.pgm
|
RayTracer -b 8 --bg-color 0.1 0.1 0.2 -o output.pgm
|
||||||
|
RayTracer --model models/bunny.obj --renderer cpu --headless -o bunny.pgm
|
||||||
|
RayTracer --model models/scene.fbx --model-scale 0.5 0.5 0.5 --renderer gpu
|
||||||
)";
|
)";
|
||||||
}
|
}
|
||||||
|
|
||||||
CLIOptions parseArgs(int argc, const char* argv[])
|
CLIOptions parseArgs(int argc, const char* argv[])
|
||||||
{
|
{
|
||||||
CLIOptions options;
|
CLIOptions options;
|
||||||
::CLI::App app{"RayTracer - A Path Tracing Renderer"};
|
std::vector<std::string> args;
|
||||||
app.set_help_flag("-h,--help", "Show this help message");
|
for (int i = 1; i < argc; ++i)
|
||||||
|
|
||||||
// Render parameters
|
|
||||||
app.add_option_group("Render Parameters")->add_option(
|
|
||||||
"-w,--width", options.width, "Image width");
|
|
||||||
app.add_option("--height", options.height, "Image height");
|
|
||||||
app.add_option("-s,--samples", options.numSamples, "Number of samples per pixel");
|
|
||||||
app.add_option("-b,--bounces", options.maxBounces, "Maximum ray bounces");
|
|
||||||
app.add_option("--throughput", options.throughputThreshold, "Minimum throughput threshold");
|
|
||||||
app.add_option("-o,--output", options.outputFile, "Output image file path");
|
|
||||||
|
|
||||||
// Background color (3 floats)
|
|
||||||
std::vector<float> bgColor(3, 0.0f);
|
|
||||||
app.add_option("--bg-color", bgColor, "Background/sky color (r g b)")
|
|
||||||
->expected(3);
|
|
||||||
|
|
||||||
// Camera parameters
|
|
||||||
std::vector<float> camPos(3, 0.0f);
|
|
||||||
app.add_option("--cam-pos", camPos, "Camera position (x y z)")
|
|
||||||
->expected(3);
|
|
||||||
std::vector<float> camTarget(3, 0.0f);
|
|
||||||
app.add_option("--cam-target", camTarget, "Camera target (x y z)")
|
|
||||||
->expected(3);
|
|
||||||
app.add_option("--cam-focal", options.camFocalLength, "Focal length");
|
|
||||||
app.add_option("--cam-aperture", options.camAperture, "Aperture size");
|
|
||||||
app.add_option("--cam-distance", options.camDistance, "Subject distance S_O");
|
|
||||||
std::vector<float> camSensor(2, 0.0f);
|
|
||||||
app.add_option("--cam-sensor", camSensor, "Sensor size (w h)")
|
|
||||||
->expected(2);
|
|
||||||
|
|
||||||
// Camera shading parameters
|
|
||||||
std::vector<float> fogEmission(3, 0.0f);
|
|
||||||
app.add_option("--fog-emission", fogEmission, "Fog emission color (r g b)")
|
|
||||||
->expected(3);
|
|
||||||
app.add_option("--specular-coeff", options.specularCoeff, "Specular coefficient ks");
|
|
||||||
app.add_option("--ambient-coeff", options.ambientCoeff, "Ambient coefficient ka");
|
|
||||||
|
|
||||||
// Light parameters - using TakeAll policy to allow multiple occurrences
|
|
||||||
std::vector<std::vector<float>> dirLightArgs;
|
|
||||||
app.add_option("--dir-light", dirLightArgs, "Add directional light (dx dy dz r g b)")
|
|
||||||
->expected(6)
|
|
||||||
->multi_option_policy(::CLI::MultiOptionPolicy::TakeAll);
|
|
||||||
std::vector<std::vector<float>> pointLightArgs;
|
|
||||||
app.add_option("--point-light", pointLightArgs, "Add point light (x y z r g b attenuation)")
|
|
||||||
->expected(7)
|
|
||||||
->multi_option_policy(::CLI::MultiOptionPolicy::TakeAll);
|
|
||||||
|
|
||||||
// Parse arguments
|
|
||||||
bool helpRequested = false;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
app.parse(argc, argv);
|
args.push_back(argv[i]);
|
||||||
}
|
|
||||||
catch (const ::CLI::CallForHelp& e)
|
|
||||||
{
|
|
||||||
helpRequested = true;
|
|
||||||
}
|
|
||||||
catch (const ::CLI::CallForAllHelp& e)
|
|
||||||
{
|
|
||||||
helpRequested = true;
|
|
||||||
}
|
|
||||||
catch (const ::CLI::ParseError& e)
|
|
||||||
{
|
|
||||||
std::cerr << e.what() << std::endl;
|
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if help was requested
|
size_t i = 0;
|
||||||
if (helpRequested || app.get_help_ptr()->count() > 0)
|
while (i < args.size())
|
||||||
{
|
{
|
||||||
options.showHelp = true;
|
const std::string& arg = args[i];
|
||||||
printHelp();
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process background color
|
if (arg == "--help")
|
||||||
if (bgColor.size() == 3)
|
|
||||||
{
|
|
||||||
options.backgroundColor = glm::vec3(bgColor[0], bgColor[1], bgColor[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process camera position
|
|
||||||
if (camPos.size() == 3)
|
|
||||||
{
|
|
||||||
options.camPosition = glm::vec3(camPos[0], camPos[1], camPos[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process camera target
|
|
||||||
if (camTarget.size() == 3)
|
|
||||||
{
|
|
||||||
options.camTarget = glm::vec3(camTarget[0], camTarget[1], camTarget[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process camera sensor size
|
|
||||||
if (camSensor.size() == 2)
|
|
||||||
{
|
|
||||||
options.camSensorSize = glm::vec2(camSensor[0], camSensor[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process fog emission
|
|
||||||
if (fogEmission.size() == 3)
|
|
||||||
{
|
|
||||||
options.fogEmission = glm::vec3(fogEmission[0], fogEmission[1], fogEmission[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process directional lights
|
|
||||||
for (const auto& args : dirLightArgs)
|
|
||||||
{
|
|
||||||
if (args.size() == 6)
|
|
||||||
{
|
{
|
||||||
|
options.showHelp = true;
|
||||||
|
printHelp();
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
else if (arg == "--width" || arg == "-w")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --width requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.width = parseUInt(args[++i], "width");
|
||||||
|
}
|
||||||
|
else if (arg == "--height" || arg == "-h")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --height requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.height = parseUInt(args[++i], "height");
|
||||||
|
}
|
||||||
|
else if (arg == "--samples" || arg == "-s")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --samples requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.numSamples = parseUInt(args[++i], "samples");
|
||||||
|
}
|
||||||
|
else if (arg == "--bounces" || arg == "-b")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --bounces requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.maxBounces = parseUInt(args[++i], "bounces");
|
||||||
|
}
|
||||||
|
else if (arg == "--bg-color")
|
||||||
|
{
|
||||||
|
if (i + 3 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --bg-color requires 3 values (r g b)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.backgroundColor = parseVec3(args, i + 1, "bg-color");
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (arg == "--throughput")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --throughput requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.throughputThreshold = parseFloat(args[++i], "throughput");
|
||||||
|
}
|
||||||
|
else if (arg == "--output" || arg == "-o")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --output requires a file path\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.outputFile = args[++i];
|
||||||
|
}
|
||||||
|
else if (arg == "--cam-pos")
|
||||||
|
{
|
||||||
|
if (i + 3 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --cam-pos requires 3 values (x y z)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.camPosition = parseVec3(args, i + 1, "cam-pos");
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (arg == "--cam-target")
|
||||||
|
{
|
||||||
|
if (i + 3 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --cam-target requires 3 values (x y z)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.camTarget = parseVec3(args, i + 1, "cam-target");
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (arg == "--cam-focal")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --cam-focal requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.camFocalLength = parseFloat(args[++i], "cam-focal");
|
||||||
|
}
|
||||||
|
else if (arg == "--cam-aperture")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --cam-aperture requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.camAperture = parseFloat(args[++i], "cam-aperture");
|
||||||
|
}
|
||||||
|
else if (arg == "--cam-distance")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --cam-distance requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.camDistance = parseFloat(args[++i], "cam-distance");
|
||||||
|
}
|
||||||
|
else if (arg == "--cam-sensor")
|
||||||
|
{
|
||||||
|
if (i + 2 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --cam-sensor requires 2 values (w h)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.camSensorSize = parseVec2(args, i + 1, "cam-sensor");
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else if (arg == "--fog-emission")
|
||||||
|
{
|
||||||
|
if (i + 3 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --fog-emission requires 3 values (r g b)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.fogEmission = parseVec3(args, i + 1, "fog-emission");
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (arg == "--specular-coeff")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --specular-coeff requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.specularCoeff = parseFloat(args[++i], "specular-coeff");
|
||||||
|
}
|
||||||
|
else if (arg == "--ambient-coeff")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --ambient-coeff requires a value\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.ambientCoeff = parseFloat(args[++i], "ambient-coeff");
|
||||||
|
}
|
||||||
|
else if (arg == "--dir-light")
|
||||||
|
{
|
||||||
|
if (i + 6 > args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --dir-light requires 6 values (dx dy dz r g b)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
DirectionalLight light;
|
DirectionalLight light;
|
||||||
light.direction = glm::vec3(args[0], args[1], args[2]);
|
light.direction = parseVec3(args, i + 1, "dir-light direction");
|
||||||
light.color = glm::vec3(args[3], args[4], args[5]);
|
light.color = parseVec3(args, i + 4, "dir-light color");
|
||||||
options.directionalLights.push_back(light);
|
options.directionalLights.push_back(light);
|
||||||
|
i += 6;
|
||||||
}
|
}
|
||||||
}
|
else if (arg == "--point-light")
|
||||||
|
|
||||||
// Process point lights
|
|
||||||
for (const auto& args : pointLightArgs)
|
|
||||||
{
|
|
||||||
if (args.size() == 7)
|
|
||||||
{
|
{
|
||||||
|
if (i + 7 > args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --point-light requires 7 values (x y z r g b attenuation)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
PointLight light;
|
PointLight light;
|
||||||
light.position = glm::vec3(args[0], args[1], args[2]);
|
light.position = parseVec3(args, i + 1, "point-light position");
|
||||||
light.color = glm::vec3(args[3], args[4], args[5]);
|
light.color = parseVec3(args, i + 4, "point-light color");
|
||||||
light.attenuation = args[6];
|
light.attenuation = parseFloat(args[i + 7], "point-light attenuation");
|
||||||
options.pointLights.push_back(light);
|
options.pointLights.push_back(light);
|
||||||
|
i += 7;
|
||||||
}
|
}
|
||||||
|
else if (arg == "--model")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --model requires a file path\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.modelPath = args[++i];
|
||||||
|
}
|
||||||
|
else if (arg == "--model-scale")
|
||||||
|
{
|
||||||
|
if (i + 3 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --model-scale requires 3 values (x y z)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.modelScale = parseVec3(args, i + 1, "model-scale");
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (arg == "--model-translate")
|
||||||
|
{
|
||||||
|
if (i + 3 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --model-translate requires 3 values (x y z)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.modelTranslation = parseVec3(args, i + 1, "model-translate");
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (arg == "--model-rotate")
|
||||||
|
{
|
||||||
|
if (i + 3 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --model-rotate requires 3 values (x y z)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.modelRotation = parseVec3(args, i + 1, "model-rotate");
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (arg == "--renderer")
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.size())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: --renderer requires a type (cpu, gpu, metal)\n";
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options.rendererType = parseRendererType(args[++i]);
|
||||||
|
}
|
||||||
|
else if (arg == "--headless")
|
||||||
|
{
|
||||||
|
options.headless = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Warning: Unknown argument: " << arg << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
@@ -222,4 +441,67 @@ Examples:
|
|||||||
renderer->addPointLight(pointLight);
|
renderer->addPointLight(pointLight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::mat4 getModelTransform(const CLIOptions& opts)
|
||||||
|
{
|
||||||
|
// Build transformation matrix: translate * rotate * scale
|
||||||
|
glm::mat4 scale = glm::scale(glm::mat4(1.0f), opts.modelScale);
|
||||||
|
glm::mat4 rotationX = glm::rotate(glm::mat4(1.0f), glm::radians(opts.modelRotation.x), glm::vec3(1, 0, 0));
|
||||||
|
glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(opts.modelRotation.y), glm::vec3(0, 1, 0));
|
||||||
|
glm::mat4 rotationZ = glm::rotate(glm::mat4(1.0f), glm::radians(opts.modelRotation.z), glm::vec3(0, 0, 1));
|
||||||
|
glm::mat4 rotation = rotationX * rotationY * rotationZ;
|
||||||
|
glm::mat4 translation = glm::translate(glm::mat4(1.0f), opts.modelTranslation);
|
||||||
|
|
||||||
|
return translation * rotation * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool saveImagePGM(const std::vector<float>& imageData, uint32_t width, uint32_t height, const std::string& filepath)
|
||||||
|
{
|
||||||
|
if (imageData.empty() || width == 0 || height == 0)
|
||||||
|
{
|
||||||
|
std::cerr << "Error: Cannot save empty image\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageData.size() < width * height * 3)
|
||||||
|
{
|
||||||
|
std::cerr << "Error: Image data size mismatch. Expected " << (width * height * 3)
|
||||||
|
<< " floats, got " << imageData.size() << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream file(filepath, std::ios::binary);
|
||||||
|
if (!file.is_open())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: Cannot open file for writing: " << filepath << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PGM P5 format header
|
||||||
|
file << "P5\n";
|
||||||
|
file << "#" << filepath << "\n"; // Comment line
|
||||||
|
file << width << " " << height << "\n";
|
||||||
|
file << "255\n"; // Max val
|
||||||
|
|
||||||
|
// Write pixel data
|
||||||
|
for (uint32_t i = 0; i < width * height * 3; ++i)
|
||||||
|
{
|
||||||
|
// Convert float [0, 1] to uint8_t [0, 255]
|
||||||
|
uint8_t val = static_cast<uint8_t>(std::clamp(imageData[i], 0.0f, 1.0f) * 255.0f);
|
||||||
|
file.put(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (file.good())
|
||||||
|
{
|
||||||
|
std::cout << "Image saved to: " << filepath << "\n";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Error: Failed to write image to: " << filepath << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-1
@@ -5,6 +5,13 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
enum class RendererType
|
||||||
|
{
|
||||||
|
CPU,
|
||||||
|
GPU, // Vulkan
|
||||||
|
Metal
|
||||||
|
};
|
||||||
|
|
||||||
struct CLIOptions
|
struct CLIOptions
|
||||||
{
|
{
|
||||||
// Render parameters
|
// Render parameters
|
||||||
@@ -33,10 +40,23 @@ struct CLIOptions
|
|||||||
std::vector<DirectionalLight> directionalLights;
|
std::vector<DirectionalLight> directionalLights;
|
||||||
std::vector<PointLight> pointLights;
|
std::vector<PointLight> pointLights;
|
||||||
|
|
||||||
|
// Model parameters
|
||||||
|
std::string modelPath;
|
||||||
|
glm::vec3 modelScale = glm::vec3(1, 1, 1);
|
||||||
|
glm::vec3 modelTranslation = glm::vec3(0, 0, 0);
|
||||||
|
glm::vec3 modelRotation = glm::vec3(0, 0, 0); // Euler angles in degrees
|
||||||
|
|
||||||
|
// Renderer selection
|
||||||
|
RendererType rendererType = RendererType::CPU;
|
||||||
|
|
||||||
|
// Headless mode (no GUI, render and exit)
|
||||||
|
bool headless = false;
|
||||||
|
|
||||||
|
// Help flag
|
||||||
bool showHelp = false;
|
bool showHelp = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace AppCLI
|
namespace CLI
|
||||||
{
|
{
|
||||||
// Parse command-line arguments and return CLIOptions
|
// Parse command-line arguments and return CLIOptions
|
||||||
CLIOptions parseArgs(int argc, const char* argv[]);
|
CLIOptions parseArgs(int argc, const char* argv[]);
|
||||||
@@ -52,4 +72,13 @@ namespace AppCLI
|
|||||||
|
|
||||||
// Apply lights from CLIOptions to renderer
|
// Apply lights from CLIOptions to renderer
|
||||||
void applyLights(Renderer* renderer, const CLIOptions& opts);
|
void applyLights(Renderer* renderer, const CLIOptions& opts);
|
||||||
|
|
||||||
|
// Generate model transform matrix from CLI options
|
||||||
|
glm::mat4 getModelTransform(const CLIOptions& opts);
|
||||||
|
|
||||||
|
// Save image buffer to PGM file
|
||||||
|
bool saveImagePGM(const std::vector<float>& imageData, uint32_t width, uint32_t height, const std::string& filepath);
|
||||||
|
|
||||||
|
// Get renderer type as string
|
||||||
|
const char* rendererTypeToString(RendererType type);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -5,7 +5,6 @@
|
|||||||
"features": [ "glfw-binding", "opengl3-binding", "metal-binding" ]
|
"features": [ "glfw-binding", "opengl3-binding", "metal-binding" ]
|
||||||
},
|
},
|
||||||
"assimp",
|
"assimp",
|
||||||
"cli11",
|
|
||||||
"ktx",
|
"ktx",
|
||||||
"glfw3",
|
"glfw3",
|
||||||
"glew",
|
"glew",
|
||||||
@@ -13,4 +12,4 @@
|
|||||||
"fmt",
|
"fmt",
|
||||||
"shader-slang"
|
"shader-slang"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user