diff --git a/src/basics/Maths.h b/src/basics/Maths.h index 1e0bd45..1538103 100644 --- a/src/basics/Maths.h +++ b/src/basics/Maths.h @@ -32,10 +32,16 @@ class BASICSSHARED_EXPORT Maths { */ static double zeroPoint(double segment_length, double edge0, double edge1); + static inline bool equals(double v1, double v2, double eps = DOUBLE_EPS) { + double d = v1 - v2; + return d <= eps and d >= -eps; + } + static constexpr double PI = 3.141592653589793238462643383279; static constexpr double PI_2 = PI / 2.0; static constexpr double PI_4 = PI / 4.0; static constexpr double TWOPI = 2.0 * PI; + static constexpr double DOUBLE_EPS = 0.0000000001; }; } } diff --git a/src/basics/Vector3.cpp b/src/basics/Vector3.cpp index 664e9d5..8cdfacb 100644 --- a/src/basics/Vector3.cpp +++ b/src/basics/Vector3.cpp @@ -65,6 +65,10 @@ Vector3 Vector3::normalize() const { } } +bool Vector3::isNormalized() const { + return Maths::equals(getNorm(), 1.0); +} + double Vector3::dotProduct(const Vector3 &other) const { return x * other.x + y * other.y + z * other.z; } diff --git a/src/basics/Vector3.h b/src/basics/Vector3.h index 4571292..86f7bdb 100644 --- a/src/basics/Vector3.h +++ b/src/basics/Vector3.h @@ -51,6 +51,7 @@ class BASICSSHARED_EXPORT Vector3 { Vector3 scale(double scaling) const; double getNorm() const; Vector3 normalize() const; + bool isNormalized() const; Vector3 add(double x, double y, double z) const; Vector3 add(const Vector3 &other) const; diff --git a/src/interface/commandline/main.cpp b/src/interface/commandline/main.cpp index 7a94d90..bede181 100644 --- a/src/interface/commandline/main.cpp +++ b/src/interface/commandline/main.cpp @@ -1,9 +1,11 @@ #include "AtmosphereDefinition.h" #include "CameraDefinition.h" #include "Logs.h" +#include "RandomGenerator.h" #include "RenderConfig.h" #include "Scenery.h" #include "SoftwareCanvasRenderer.h" +#include "SoftwareRayRenderer.h" #include "TimeManager.h" #include @@ -17,7 +19,10 @@ static void displayHelp() { printf(" -h Show this help\n"); printf(" -ts Run the render test suite\n"); printf(" -f x Saved file to load (str)\n"); - printf(" -n Number of pictures in the sequence\n"); + printf(" -r x Random seed to use (int)\n"); + printf(" -s x First picture in the sequence (int)\n"); + printf(" -n x Number of pictures in the sequence (int)\n"); + printf(" -rt Use ray tracing renderer instead of rasterization\n"); printf(" -rw x Render width (int)\n"); printf(" -rh x Render height (int)\n"); printf(" -rq x Render quality (int, 1 to 10)\n"); @@ -37,6 +42,7 @@ int main(int argc, char **argv) { RenderConfig conf_render_params(480, 270, 1, 3); int conf_first_picture = 0; int conf_nb_pictures = 1; + long unsigned int conf_seed = 0; double conf_daytime_start = -1.0; double conf_daytime_step = 0.0; double conf_camera_step_x = 0.0; @@ -46,6 +52,7 @@ int main(int argc, char **argv) { double conf_wind_z = 0.0; int outputcount; char outputpath[500]; + bool raytracing = false; argc--; argv++; @@ -57,10 +64,16 @@ int main(int argc, char **argv) { } else if (strcmp(*argv, "-ts") == 0 || strcmp(*argv, "--testsuite") == 0) { runTestSuite(); return 0; + } else if (strcmp(*argv, "-rt") == 0 || strcmp(*argv, "--raytracing") == 0) { + raytracing = true; } else if (strcmp(*argv, "-f") == 0 || strcmp(*argv, "--file") == 0) { if (argc--) { conf_file_path = *(++argv); } + } else if (strcmp(*argv, "-r") == 0 || strcmp(*argv, "--randomseed") == 0) { + if (argc--) { + conf_seed = atoi(*(++argv)); + } } else if (strcmp(*argv, "-s") == 0 || strcmp(*argv, "--start") == 0) { if (argc--) { conf_first_picture = atoi(*(++argv)); @@ -120,10 +133,15 @@ int main(int argc, char **argv) { printf("Initializing ...\n"); Scenery *scenery = new Scenery(); + RandomGenerator generator(conf_seed); if (conf_file_path) { scenery->loadGlobal(conf_file_path); } else { + if (conf_seed) { + RandomGeneratorDefault = generator; + } + printf("Using seed %lu\n", RandomGeneratorDefault.getSeed()); scenery->autoPreset(); } @@ -140,7 +158,7 @@ int main(int argc, char **argv) { Vector3 step = {conf_camera_step_x, conf_camera_step_y, conf_camera_step_z}; camera->setLocation(camera->getLocation().add(step)); - renderer = new SoftwareCanvasRenderer(scenery); + renderer = raytracing ? new SoftwareRayRenderer(scenery) : new SoftwareCanvasRenderer(scenery); renderer->setConfig(conf_render_params); if (outputcount >= conf_first_picture) { diff --git a/src/interface/modeler/RenderProcess.cpp b/src/interface/modeler/RenderProcess.cpp index 194b7e6..ba8d02f 100644 --- a/src/interface/modeler/RenderProcess.cpp +++ b/src/interface/modeler/RenderProcess.cpp @@ -7,7 +7,7 @@ #include "RenderConfig.h" #include "RenderPreviewProvider.h" #include "RenderProgress.h" -#include "SoftwareCanvasRenderer.h" +#include "SoftwareRayRenderer.h" #include "Thread.h" #include #include @@ -99,7 +99,8 @@ void RenderProcess::startRender(Scenery *scenery, const RenderConfig &config) { delete renderer; } - renderer = new SoftwareCanvasRenderer(scenery); + // renderer = new SoftwareCanvasRenderer(scenery); + renderer = new SoftwareRayRenderer(scenery); renderer->setConfig(config); destination->setCanvas(renderer->getCanvas()); diff --git a/src/render/software/RayCastingManager.cpp b/src/render/software/RayCastingManager.cpp index 563039b..31b4c68 100644 --- a/src/render/software/RayCastingManager.cpp +++ b/src/render/software/RayCastingManager.cpp @@ -1,4 +1,62 @@ #include "RayCastingManager.h" -RayCastingManager::RayCastingManager() { +#include "Color.h" +#include "RayIntersector.h" +#include "Vector3.h" +#include +#include + +class RayCastingManager::pimpl { + public: + vector> intersectors; +}; + +RayCastingManager::RayCastingManager() : impl(new pimpl()) { +} + +RayCastingManager::~RayCastingManager() { +} + +int RayCastingManager::getIntersectorCount() { + return (int)impl->intersectors.size(); +} + +shared_ptr RayCastingManager::getIntersector(int position) { + if (position >= 0 && position < getIntersectorCount()) { + return impl->intersectors[position]; + } else { + return nullptr; + } +} + +void RayCastingManager::unregisterAllIntersectors() { + impl->intersectors.clear(); +} + +static bool _cmp_priority(const shared_ptr i1, const shared_ptr i2) { + return i1->getPriority() > i2->getPriority(); +} + +void RayCastingManager::registerIntersector(shared_ptr intersector) { + impl->intersectors.push_back(intersector); + sort(impl->intersectors.begin(), impl->intersectors.end(), _cmp_priority); +} + +Color RayCastingManager::getFinal(const Vector3 &eye, const Vector3 &direction) const { + double limit = 10000.0; + shared_ptr hit; + Vector3 hit_location; + + for (auto &intersector : impl->intersectors) { + if (intersector->findIntersection(eye, direction, limit, &hit_location)) { + limit = hit_location.sub(eye).getNorm(); + hit = intersector; + } + } + + if (hit) { + return hit->getColorAtHit(eye, hit_location); + } else { + return COLOR_BLACK; + } } diff --git a/src/render/software/RayCastingManager.h b/src/render/software/RayCastingManager.h index b8f10a3..34814f5 100644 --- a/src/render/software/RayCastingManager.h +++ b/src/render/software/RayCastingManager.h @@ -2,15 +2,44 @@ #include "software_global.h" +#include + namespace paysages { namespace software { -typedef RayCastingResult (*FuncGeneralCastRay)(SoftwareRenderer *renderer, const Vector3 &start, - const Vector3 &direction); - +/** + * Manager of ray intersectors to perform ray casting on a scenery. + */ class SOFTWARESHARED_EXPORT RayCastingManager { public: RayCastingManager(); + virtual ~RayCastingManager(); + + /** + * Get the number of registered intersectors. + */ + int getIntersectorCount(); + + shared_ptr getIntersector(int position); + + /** + * Clear of all registered intersectors. + */ + void unregisterAllIntersectors(); + + /** + * Register a new intersector. + */ + void registerIntersector(shared_ptr intersector); + + /** + * Get the final color received along a single ray. + */ + Color getFinal(const Vector3 &eye, const Vector3 &direction) const; + + private: + class pimpl; + unique_ptr impl; }; } } diff --git a/src/render/software/RayIntersector.cpp b/src/render/software/RayIntersector.cpp new file mode 100644 index 0000000..a3136ad --- /dev/null +++ b/src/render/software/RayIntersector.cpp @@ -0,0 +1,25 @@ +#include "RayIntersector.h" + +#include "Color.h" + +RayIntersector::RayIntersector() { +} + +RayIntersector::~RayIntersector() { +} + +int RayIntersector::getPriority() const { + return 0; +} + +bool RayIntersector::findIntersection(const Vector3 &, const Vector3 &, double, Vector3 *) const { + return false; +} + +bool RayIntersector::isInside(const Vector3 &, double *) const { + return false; +} + +Color RayIntersector::getColorAtHit(const Vector3 &, const Vector3 &) const { + return COLOR_BLACK; +} diff --git a/src/render/software/RayIntersector.h b/src/render/software/RayIntersector.h new file mode 100644 index 0000000..45fe762 --- /dev/null +++ b/src/render/software/RayIntersector.h @@ -0,0 +1,43 @@ +#pragma once + +#include "software_global.h" + +namespace paysages { +namespace software { + +/** + * Abstract class to handle intersection with a ray. + */ +class SOFTWARESHARED_EXPORT RayIntersector { + public: + RayIntersector(); + virtual ~RayIntersector(); + + /** + * Get the priority of this intersector. + * + * Higher priority intersectors are tested first, and should be the easy ones. + */ + virtual int getPriority() const; + + /** + * Find the nearest intersection with the ray (within *limit* length). + * + * By default, it will perform ray marching, using isInside as intersection checker. + */ + virtual bool findIntersection(const Vector3 &eye, const Vector3 &direction, double limit, Vector3 *out_hit) const; + + /** + * Indicate if a given location is inside a solid volume. + */ + virtual bool isInside(const Vector3 &location, double *out_distance = nullptr) const; + + /** + * Get the incident color for a ray intersection at a given hit point. + * + * This should already have lighting apply, but not medium traversal between *eye* and *location*. + */ + virtual Color getColorAtHit(const Vector3 &eye, const Vector3 &location) const; +}; +} +} diff --git a/src/render/software/SkyIntersector.cpp b/src/render/software/SkyIntersector.cpp new file mode 100644 index 0000000..45e2ba0 --- /dev/null +++ b/src/render/software/SkyIntersector.cpp @@ -0,0 +1,35 @@ +#include "SkyIntersector.h" + +#include "AtmosphereRenderer.h" +#include "AtmosphereResult.h" +#include "Color.h" +#include "Maths.h" +#include "Scenery.h" +#include "Vector3.h" +#include + +SkyIntersector::SkyIntersector(BaseAtmosphereRenderer *atmosphere) : atmosphere(atmosphere) { +} + +int SkyIntersector::getPriority() const { + return 1000; +} + +bool SkyIntersector::findIntersection(const Vector3 &eye, const Vector3 &direction, double, Vector3 *out_hit) const { + assert(direction.isNormalized()); + + if (eye.y >= Scenery::ATMOSPHERE_WIDTH_SCALED) { + // Above atmosphere, intersect right in front of the eye + *out_hit = eye.add(direction.scale(0.00001)); + } else { + // Intersect with upper atmosphere boundary + // TODO + *out_hit = eye.add(direction.scale(0.00001)); + } + + return true; +} + +Color SkyIntersector::getColorAtHit(const Vector3 &eye, const Vector3 &location) const { + return atmosphere->getSkyColor(location.sub(eye).normalize()).final; +} diff --git a/src/render/software/SkyIntersector.h b/src/render/software/SkyIntersector.h new file mode 100644 index 0000000..0f7ee28 --- /dev/null +++ b/src/render/software/SkyIntersector.h @@ -0,0 +1,28 @@ +#pragma once + +#include "software_global.h" + +#include "RayIntersector.h" + +namespace paysages { +namespace software { + +/** + * Ray intersector with sky. + * + * This will always hit the upper atmosphere limit, in any direction. + * If already in the upper atmosphere, it will hit immediately in front of the eye. + */ +class SOFTWARESHARED_EXPORT SkyIntersector : public RayIntersector { + public: + SkyIntersector(BaseAtmosphereRenderer *atmosphere); + virtual int getPriority() const override; + virtual bool findIntersection(const Vector3 &eye, const Vector3 &direction, double limit, + Vector3 *out_hit) const override; + virtual Color getColorAtHit(const Vector3 &eye, const Vector3 &location) const override; + + private: + BaseAtmosphereRenderer *atmosphere; +}; +} +} diff --git a/src/render/software/SoftwareCanvasRenderer.cpp b/src/render/software/SoftwareCanvasRenderer.cpp index 6ac5814..df02e9b 100644 --- a/src/render/software/SoftwareCanvasRenderer.cpp +++ b/src/render/software/SoftwareCanvasRenderer.cpp @@ -96,21 +96,7 @@ void SoftwareCanvasRenderer::render() { for (int y = 0; y < ny; y++) { for (int x = 0; x < nx; x++) { CanvasPortion *portion = canvas->at(x, y); - - progress->enterSub(2); - - if (not interrupted) { - portion->preparePixels(); - rasterize(portion); - } - - if (not interrupted and postprocess_enabled) { - applyPixelShader(portion); - } - - portion->discardPixels(); - - progress->exitSub(); + renderPortion(portion, progress, &interrupted); } } progress->exitSub(); @@ -174,3 +160,20 @@ void SoftwareCanvasRenderer::applyPixelShader(CanvasPortion *portion) { } progress->exitSub(); } + +void SoftwareCanvasRenderer::renderPortion(CanvasPortion *portion, RenderProgress *progress, bool *interrupt) { + progress->enterSub(2); + + if (not*interrupt) { + portion->preparePixels(); + rasterize(portion); + } + + if (not*interrupt and postprocess_enabled) { + applyPixelShader(portion); + } + + portion->discardPixels(); + + progress->exitSub(); +} diff --git a/src/render/software/SoftwareCanvasRenderer.h b/src/render/software/SoftwareCanvasRenderer.h index a3bdf57..6189d0b 100644 --- a/src/render/software/SoftwareCanvasRenderer.h +++ b/src/render/software/SoftwareCanvasRenderer.h @@ -108,6 +108,11 @@ class SOFTWARESHARED_EXPORT SoftwareCanvasRenderer : public SoftwareRenderer { */ void applyPixelShader(CanvasPortion *portion); + /** + * Perform rendering on a single canvas portion. + */ + virtual void renderPortion(CanvasPortion *portion, RenderProgress *progress, bool *interrupt); + private: RenderProgress *progress; diff --git a/src/render/software/SoftwareRayRenderer.cpp b/src/render/software/SoftwareRayRenderer.cpp new file mode 100644 index 0000000..2f0b289 --- /dev/null +++ b/src/render/software/SoftwareRayRenderer.cpp @@ -0,0 +1,62 @@ +#include "SoftwareRayRenderer.h" + +#include "CameraDefinition.h" +#include "CanvasPortion.h" +#include "Color.h" +#include "RayCastingManager.h" +#include "RenderProgress.h" +#include "Scenery.h" +#include "SkyIntersector.h" +#include "TerrainIntersector.h" +#include "Vector3.h" + +class SoftwareRayRenderer::pimpl { + public: + RayCastingManager manager; +}; + +SoftwareRayRenderer::SoftwareRayRenderer(Scenery *scenery, bool standard) + : SoftwareCanvasRenderer(scenery), impl(new pimpl) { + if (standard) { + registerStandardIntersectors(); + } +} + +SoftwareRayRenderer::~SoftwareRayRenderer() { +} + +void SoftwareRayRenderer::registerStandardIntersectors() { + impl->manager.unregisterAllIntersectors(); + + impl->manager.registerIntersector(make_shared(getAtmosphereRenderer())); + impl->manager.registerIntersector(make_shared(getTerrainRenderer())); +} + +void SoftwareRayRenderer::renderPortion(CanvasPortion *portion, RenderProgress *progress, bool *interrupt) { + int width = portion->getWidth(); + int height = portion->getHeight(); + + portion->preparePixels(); + progress->enterSub(width * height); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + Vector3 direction = + Vector3(1.0, to_double(y) / to_double(height) - 0.5, to_double(x) / to_double(width) - 0.5).normalize(); + Color color = impl->manager.getFinal(getCameraLocation(), direction); + portion->setColor(x, y, color); + progress->add(); + } + if (*interrupt) { + return; + } + } + + portion->discardPixels(); + progress->exitSub(); +} + +void SoftwareRayRenderer::prepare() { + SoftwareCanvasRenderer::prepare(); + registerStandardIntersectors(); +} diff --git a/src/render/software/SoftwareRayRenderer.h b/src/render/software/SoftwareRayRenderer.h new file mode 100644 index 0000000..f95d7d6 --- /dev/null +++ b/src/render/software/SoftwareRayRenderer.h @@ -0,0 +1,43 @@ +#pragma once + +#include "software_global.h" + +#include "SoftwareCanvasRenderer.h" + +#include + +namespace paysages { +namespace software { + +/** + * Software rendering using only ray tracing, not rasterization. + * + * Follows these steps, for each view ray : + * - Find a ray intersection with solid matter + * - Texture/light the solid matter (may need other secondary rays) + * - Apply medium traversal along the ray (e.g. atmosphere) + */ +class SOFTWARESHARED_EXPORT SoftwareRayRenderer : public SoftwareCanvasRenderer { + public: + SoftwareRayRenderer(Scenery *scenery, bool standard = true); + virtual ~SoftwareRayRenderer(); + + /** + * Register standard scenery intersectors. + */ + void registerStandardIntersectors(); + + protected: + /** + * Render a single canvas portion using ray-tracing. + */ + virtual void renderPortion(CanvasPortion *portion, RenderProgress *progress, bool *interrupt); + + virtual void prepare() override; + + private: + class pimpl; + unique_ptr impl; +}; +} +} diff --git a/src/render/software/TerrainIntersector.cpp b/src/render/software/TerrainIntersector.cpp new file mode 100644 index 0000000..48beaac --- /dev/null +++ b/src/render/software/TerrainIntersector.cpp @@ -0,0 +1,26 @@ +#include "TerrainIntersector.h" + +#include "TerrainRenderer.h" +#include "RayCastingResult.h" + +TerrainIntersector::TerrainIntersector(TerrainRenderer *renderer) : renderer(renderer) { +} + +int TerrainIntersector::getPriority() const { + return 0; +} + +bool TerrainIntersector::findIntersection(const Vector3 &eye, const Vector3 &direction, double, + Vector3 *out_hit) const { + auto result = renderer->castRay(eye, direction); + if (result.hit) { + *out_hit = result.hit_location; + return true; + } else { + return false; + } +} + +Color TerrainIntersector::getColorAtHit(const Vector3 &, const Vector3 &location) const { + return renderer->getFinalColor(location.x, location.z, 0.0001); +} diff --git a/src/render/software/TerrainIntersector.h b/src/render/software/TerrainIntersector.h new file mode 100644 index 0000000..e369afc --- /dev/null +++ b/src/render/software/TerrainIntersector.h @@ -0,0 +1,25 @@ +#pragma once + +#include "software_global.h" + +#include "RayIntersector.h" + +namespace paysages { +namespace software { + +/** + * Ray intersector with terrain. + */ +class SOFTWARESHARED_EXPORT TerrainIntersector : public RayIntersector { + public: + TerrainIntersector(TerrainRenderer *renderer); + virtual int getPriority() const override; + virtual bool findIntersection(const Vector3 &eye, const Vector3 &direction, double limit, + Vector3 *out_hit) const override; + virtual Color getColorAtHit(const Vector3 &eye, const Vector3 &location) const override; + + private: + TerrainRenderer *renderer; +}; +} +} diff --git a/src/render/software/software_global.h b/src/render/software/software_global.h index fee1f8a..dc8dfad 100644 --- a/src/render/software/software_global.h +++ b/src/render/software/software_global.h @@ -12,6 +12,7 @@ namespace paysages { namespace software { class SoftwareRenderer; class SoftwareCanvasRenderer; +class SoftwareRayRenderer; class RenderConfig; class RenderProgress; @@ -46,6 +47,8 @@ class LightSource; class RayCastingManager; class RayCastingResult; +class RayIntersector; +class SkyIntersector; class NightSky; class MoonRenderer; diff --git a/src/system/RandomGenerator.cpp b/src/system/RandomGenerator.cpp index 4ea9779..25acacd 100644 --- a/src/system/RandomGenerator.cpp +++ b/src/system/RandomGenerator.cpp @@ -25,6 +25,7 @@ RandomGenerator::RandomGenerator(RandomGenerator::Seed seed) { } } data = new RandomGeneratorPrivate(seed); + this->seed = seed; } RandomGenerator::~RandomGenerator() { diff --git a/src/tests/CameraDefinition_Test.cpp b/src/tests/CameraDefinition_Test.cpp index 5f0efdc..6d57c5a 100644 --- a/src/tests/CameraDefinition_Test.cpp +++ b/src/tests/CameraDefinition_Test.cpp @@ -26,6 +26,13 @@ TEST(CameraDefinition, unproject) { point = cam.project(Vector3(-25.1, 8.3, 1.3)); point = cam.unproject(point); EXPECT_VECTOR3_COORDS(point, -25.1, 8.3, 1.3); + + cam.setLocationCoords(0.3, 0.8, -2.4); + cam.setTargetCoords(5.2, -4.1, 1.5); + + point = cam.project(Vector3(12.4, -1.3, 3.4)); + point = cam.unproject(point); + EXPECT_VECTOR3_COORDS(point, 12.4, -1.3, 3.4); } TEST(CameraDefinition, getRealDepth) { diff --git a/src/tests/RayCastingManager_Test.cpp b/src/tests/RayCastingManager_Test.cpp new file mode 100644 index 0000000..1750a1a --- /dev/null +++ b/src/tests/RayCastingManager_Test.cpp @@ -0,0 +1,38 @@ +#include "RayCastingManager.h" +#include "BaseTestCase.h" + +#include "RayIntersector.h" + +class _FakeIntersectorPriority : public RayIntersector { + public: + _FakeIntersectorPriority(int priority) : priority(priority) { + } + virtual int getPriority() const override { + return priority; + } + int priority; +}; + +TEST(RayCastingManager, registerIntersector) { + RayCastingManager manager; + + EXPECT_EQ(0, manager.getIntersectorCount()); + + manager.registerIntersector(make_shared<_FakeIntersectorPriority>(8)); + + ASSERT_EQ(1, manager.getIntersectorCount()); + EXPECT_EQ(8, ((_FakeIntersectorPriority *)(manager.getIntersector(0).get()))->priority); + + manager.registerIntersector(make_shared<_FakeIntersectorPriority>(15)); + + ASSERT_EQ(2, manager.getIntersectorCount()); + EXPECT_EQ(15, ((_FakeIntersectorPriority *)(manager.getIntersector(0).get()))->priority); + EXPECT_EQ(8, ((_FakeIntersectorPriority *)(manager.getIntersector(1).get()))->priority); + + manager.registerIntersector(make_shared<_FakeIntersectorPriority>(2)); + + ASSERT_EQ(3, manager.getIntersectorCount()); + EXPECT_EQ(15, ((_FakeIntersectorPriority *)(manager.getIntersector(0).get()))->priority); + EXPECT_EQ(8, ((_FakeIntersectorPriority *)(manager.getIntersector(1).get()))->priority); + EXPECT_EQ(2, ((_FakeIntersectorPriority *)(manager.getIntersector(2).get()))->priority); +}