diff --git a/TODO b/TODO index 89a81eb..c29fc65 100644 --- a/TODO +++ b/TODO @@ -3,6 +3,7 @@ Technlology Preview 2 : - Streamline the definition system, with undo and events. - Implement copy-on-write on definitions (to avoid copying whole heightmaps for instance). - Add clouds to OpenGL with 3d textures. +- Refactor medium traversal to unify clouds, atmosphere and god rays. - Fix potential holes in land rendering (OpenGL and software). - Fix polygon culling near the camera in low-res renders (automatic tessellation ?). - Fix sun size not being consistent between opengl and software diff --git a/src/basics/Interpolation.cpp b/src/basics/Interpolation.cpp index 9268131..9ea3c6c 100644 --- a/src/basics/Interpolation.cpp +++ b/src/basics/Interpolation.cpp @@ -11,3 +11,17 @@ double Interpolation::bicubic(double stencil[16], double x, double y) return Interpolation::cubic(buf_cubic_y, y); } + +double Interpolation::bilinear(double p[4], double x, double y) +{ + double e1 = linear(p[0], p[1], x); + double e2 = linear(p[3], p[2], x); + return Interpolation::linear(e1, e2, y); +} + +double Interpolation::trilinear(double p[8], double x, double y, double z) +{ + double f1 = bilinear(p, x, y); + double f2 = bilinear(p + 4, x, y); + return Interpolation::linear(f1, f2, z); +} diff --git a/src/basics/Interpolation.h b/src/basics/Interpolation.h index 0c4043c..77f35c5 100644 --- a/src/basics/Interpolation.h +++ b/src/basics/Interpolation.h @@ -9,6 +9,13 @@ namespace basics { class BASICSSHARED_EXPORT Interpolation { public: + static inline double linear(double p1, double p2, double x) + { + return p1 + x * (p2 - p1); + } + static double bilinear(double p[4], double x, double y); + static double trilinear(double p[8], double x, double y, double z); + static inline double cubic(double p[4], double x) { return p[1] + 0.5 * x * (p[2] - p[0] + x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + x * (3.0 * (p[1] - p[2]) + p[3] - p[0]))); diff --git a/src/basics/SpaceSegment.h b/src/basics/SpaceSegment.h index 4abfe86..a7ffd8b 100644 --- a/src/basics/SpaceSegment.h +++ b/src/basics/SpaceSegment.h @@ -20,6 +20,10 @@ public: inline Vector3 getStart() const {return start;} inline Vector3 getEnd() const {return end;} + inline double getXDiff() const {return end.x - start.x;} + inline double getYDiff() const {return end.y - start.y;} + inline double getZDiff() const {return end.z - start.z;} + /*! * \brief Keep only the intersection with a slice orthogonal to the Y axis. * \return true if a segment remains diff --git a/src/definition/AtmosphereDefinition.cpp b/src/definition/AtmosphereDefinition.cpp index cd7935f..e96c8cd 100644 --- a/src/definition/AtmosphereDefinition.cpp +++ b/src/definition/AtmosphereDefinition.cpp @@ -3,10 +3,12 @@ #include "PackStream.h" #include "RandomGenerator.h" #include "FloatNode.h" +#include "GodRaysDefinition.h" AtmosphereDefinition::AtmosphereDefinition(DefinitionNode* parent): DefinitionNode(parent, "atmosphere", "atmosphere") { + godrays = new GodRaysDefinition(this); daytime = new FloatNode(this, "daytime"); humidity = new FloatNode(this, "humidity"); sun_radius = new FloatNode(this, "sun_radius"); diff --git a/src/definition/AtmosphereDefinition.h b/src/definition/AtmosphereDefinition.h index 0d61340..20f43ee 100644 --- a/src/definition/AtmosphereDefinition.h +++ b/src/definition/AtmosphereDefinition.h @@ -45,6 +45,7 @@ public: virtual void copy(DefinitionNode* destination) const override; + inline GodRaysDefinition *childGodRays() const {return godrays;} inline FloatNode *propDayTime() const {return daytime;} inline FloatNode *propHumidity() const {return humidity;} inline FloatNode *propSunRadius() const {return sun_radius;} @@ -78,6 +79,7 @@ public: std::vector stars; private: + GodRaysDefinition *godrays; FloatNode *humidity; FloatNode *daytime; FloatNode *sun_radius; diff --git a/src/definition/GodRaysDefinition.cpp b/src/definition/GodRaysDefinition.cpp new file mode 100644 index 0000000..94ec938 --- /dev/null +++ b/src/definition/GodRaysDefinition.cpp @@ -0,0 +1,12 @@ +#include "GodRaysDefinition.h" + +#include "FloatNode.h" + +GodRaysDefinition::GodRaysDefinition(DefinitionNode *parent): + DefinitionNode(parent, "godrays", "godrays") +{ + penetration = new FloatNode(this, "penetration", 0.01); + resistance = new FloatNode(this, "resistance", 0.3); + boost = new FloatNode(this, "boost", 8.0); +} + diff --git a/src/definition/GodRaysDefinition.h b/src/definition/GodRaysDefinition.h new file mode 100644 index 0000000..52775ec --- /dev/null +++ b/src/definition/GodRaysDefinition.h @@ -0,0 +1,29 @@ +#ifndef GODRAYSDEFINITION_H +#define GODRAYSDEFINITION_H + +#include "definition_global.h" + +#include "DefinitionNode.h" + +namespace paysages { +namespace definition { + +class DEFINITIONSHARED_EXPORT GodRaysDefinition: public DefinitionNode +{ +public: + GodRaysDefinition(DefinitionNode *parent); + + inline FloatNode *propPenetration() const {return penetration;} + inline FloatNode *propResistance() const {return resistance;} + inline FloatNode *propBoost() const {return boost;} + +private: + FloatNode *penetration; + FloatNode *resistance; + FloatNode *boost; +}; + +} +} + +#endif // GODRAYSDEFINITION_H diff --git a/src/definition/definition.pro b/src/definition/definition.pro index 7105ccf..c831134 100644 --- a/src/definition/definition.pro +++ b/src/definition/definition.pro @@ -37,7 +37,8 @@ SOURCES += \ DiffManager.cpp \ DefinitionWatcher.cpp \ IntNode.cpp \ - IntDiff.cpp + IntDiff.cpp \ + GodRaysDefinition.cpp HEADERS +=\ definition_global.h \ @@ -64,7 +65,8 @@ HEADERS +=\ DiffManager.h \ DefinitionWatcher.h \ IntNode.h \ - IntDiff.h + IntDiff.h \ + GodRaysDefinition.h unix:!symbian { maemo5 { diff --git a/src/definition/definition_global.h b/src/definition/definition_global.h index 9f2d446..e2e6903 100644 --- a/src/definition/definition_global.h +++ b/src/definition/definition_global.h @@ -30,6 +30,7 @@ namespace definition { class CloudsDefinition; class CloudLayerDefinition; class AtmosphereDefinition; + class GodRaysDefinition; class TexturesDefinition; class TextureLayerDefinition; class TerrainDefinition; diff --git a/src/interface/commandline/tests.cpp b/src/interface/commandline/tests.cpp index d8786d1..6260484 100644 --- a/src/interface/commandline/tests.cpp +++ b/src/interface/commandline/tests.cpp @@ -5,11 +5,17 @@ #include "TerrainDefinition.h" #include "AtmosphereDefinition.h" #include "TexturesDefinition.h" +#include "GodRaysDefinition.h" #include "TextureLayerDefinition.h" #include "WaterDefinition.h" #include "SurfaceMaterial.h" #include "FloatNode.h" #include "SkyRasterizer.h" +#include "CloudsDefinition.h" +#include "LightComponent.h" +#include "LightingManager.h" +#include "LightFilter.h" +#include "GodRaysSampler.h" #include @@ -103,10 +109,94 @@ static void testCloudQuality() } } +static void testGodRays() +{ + class TestLightFilter: public LightFilter + { + virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override + { + if (Vector3(0.0, 100.0, 0.0).sub(at).normalize().y > 0.97) + { + light.color = COLOR_BLACK; + return false; + } + else + { + return true; + } + } + }; + class TestRenderer: public SoftwareCanvasRenderer + { + public: + TestRenderer(Scenery *scenery): SoftwareCanvasRenderer(scenery) {} + private: + virtual void prepare() override + { + SoftwareRenderer::prepare(); + + getLightingManager()->clearSources(); + getLightingManager()->addStaticLight(LightComponent(COLOR_WHITE, VECTOR_DOWN)); + getGodRaysSampler()->setAltitudes(0.0, 30.0); + getGodRaysSampler()->reset(); + } + }; + + Scenery scenery; + scenery.autoPreset(63); + scenery.getAtmosphere()->setDayTime(12); + scenery.getCamera()->setLocation(Vector3(0.0, 1.0, -50.0)); + scenery.getCamera()->setTarget(Vector3(0.0, 15.0, 0.0)); + scenery.getTerrain()->height = 0.0; + scenery.getTerrain()->validate(); + scenery.getClouds()->clear(); + + TestRenderer renderer(&scenery); + renderer.setSize(500, 300); + SkyRasterizer *rasterizer = new SkyRasterizer(&renderer, renderer.getProgressHelper(), 0); + renderer.setSoloRasterizer(rasterizer); + TestLightFilter filter; + renderer.getLightingManager()->clearFilters(); + renderer.getLightingManager()->registerFilter(&filter); + + // quality + for (int i = 0; i < 6; i++) + { + renderer.setQuality((double)i / 5.0); + rasterizer->setQuality(0.2); + startTestRender(&renderer, "god_rays_quality", i); + } + renderer.setQuality(0.5); + + // penetration + for (int i = 0; i < 3; i++) + { + scenery.getAtmosphere()->childGodRays()->propPenetration()->setValue(0.01 + 0.02 * (double)i); + startTestRender(&renderer, "god_rays_penetration", i); + } + + // resistance + scenery.getAtmosphere()->childGodRays()->propPenetration()->setValue(0.01); + for (int i = 0; i < 3; i++) + { + scenery.getAtmosphere()->childGodRays()->propResistance()->setValue(0.1 + 0.1 * (double)i); + startTestRender(&renderer, "god_rays_resistance", i); + } + + // boost + scenery.getAtmosphere()->childGodRays()->propResistance()->setValue(0.3); + for (int i = 0; i < 3; i++) + { + scenery.getAtmosphere()->childGodRays()->propBoost()->setValue(2.0 + 4.0 * (double)i); + startTestRender(&renderer, "god_rays_boost", i); + } +} + void runTestSuite() { testGroundShadowQuality(); testRasterizationQuality(); testCloudQuality(); + testGodRays(); } diff --git a/src/render/opengl/OpenGLRenderer.cpp b/src/render/opengl/OpenGLRenderer.cpp index 93a9b67..291e607 100644 --- a/src/render/opengl/OpenGLRenderer.cpp +++ b/src/render/opengl/OpenGLRenderer.cpp @@ -9,6 +9,7 @@ #include "CloudsRenderer.h" #include "Scenery.h" #include "LightingManager.h" +#include "GodRaysSampler.h" #include "Logs.h" #include "Vector3.h" @@ -71,6 +72,7 @@ void OpenGLRenderer::initialize() getCloudsRenderer()->setEnabled(false); getLightingManager()->setSpecularity(false); + getGodRaysSampler()->setEnabled(false); skybox->initialize(); skybox->updateScenery(); diff --git a/src/render/software/AtmosphereRenderer.cpp b/src/render/software/AtmosphereRenderer.cpp index 15218b8..9ae2ff4 100644 --- a/src/render/software/AtmosphereRenderer.cpp +++ b/src/render/software/AtmosphereRenderer.cpp @@ -5,6 +5,7 @@ #include "AtmosphereDefinition.h" #include "AtmosphereModelBruneton.h" #include "AtmosphereResult.h" +#include "GodRaysSampler.h" #include "LightComponent.h" #include "LightStatus.h" #include "Scenery.h" @@ -98,7 +99,7 @@ AtmosphereResult BaseAtmosphereRenderer::getSkyColor(Vector3) return result; } -Vector3 BaseAtmosphereRenderer::getSunDirection(bool cache) const +Vector3 BaseAtmosphereRenderer::getSunDirection(bool) const { AtmosphereDefinition* atmosphere = getDefinition(); double sun_angle = (atmosphere->propDayTime()->getValue() + 0.75) * M_PI * 2.0; @@ -131,7 +132,7 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::applyAerialPerspective(Vect AtmosphereDefinition* definition = getDefinition(); AtmosphereResult result; - /* Get base perspective */ + // Get base perspective switch (definition->model) { case AtmosphereDefinition::ATMOSPHERE_MODEL_BRUNETON: @@ -141,7 +142,10 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::applyAerialPerspective(Vect ; } - /* Apply weather effects */ + // Apply god rays ponderation + result.inscattering = parent->getGodRaysSampler()->apply(COLOR_BLACK, result.inscattering, location); + + // Apply weather effects _applyWeatherEffects(definition, &result); return result; @@ -162,10 +166,10 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::getSkyColor(Vector3 directi base = COLOR_BLACK; - /* Get night sky */ + // Get night sky base = base.add(parent->getNightSky()->getColor(camera_location.y, direction)); - /* Get sun shape */ + // Get sun shape /*if (v3Dot(sun_direction, direction) >= 0) { double sun_radius = definition->sun_radius * SUN_RADIUS_SCALED * 5.0; // FIXME Why should we multiply by 5 ? @@ -190,7 +194,7 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::getSkyColor(Vector3 directi } }*/ - /* Get scattering */ + // Get scattering AtmosphereResult result; Vector3 location = camera_location.add(direction.scale(6421.0)); switch (definition->model) @@ -202,7 +206,10 @@ AtmosphereResult SoftwareBrunetonAtmosphereRenderer::getSkyColor(Vector3 directi result = BaseAtmosphereRenderer::applyAerialPerspective(location, result.base); } - /* Apply weather effects */ + // Apply god rays ponderation + result.inscattering = parent->getGodRaysSampler()->apply(COLOR_BLACK, result.inscattering, location); + + // Apply weather effects _applyWeatherEffects(definition, &result); return result; diff --git a/src/render/software/CloudBasicLayerRenderer.cpp b/src/render/software/CloudBasicLayerRenderer.cpp index 22e7167..b966265 100644 --- a/src/render/software/CloudBasicLayerRenderer.cpp +++ b/src/render/software/CloudBasicLayerRenderer.cpp @@ -239,7 +239,7 @@ bool CloudBasicLayerRenderer::alterLight(BaseCloudsModel *model, LightComponent* } } - double miminum_light = 0.5; + double miminum_light = 0.3; factor = 1.0 - (1.0 - miminum_light) * factor; light->color.r *= factor; diff --git a/src/render/software/CloudsRenderer.cpp b/src/render/software/CloudsRenderer.cpp index db8c19e..d2a700f 100644 --- a/src/render/software/CloudsRenderer.cpp +++ b/src/render/software/CloudsRenderer.cpp @@ -198,3 +198,23 @@ bool CloudsRenderer::applyLightFilter(LightComponent &light, const Vector3 &at) return true; } + +double CloudsRenderer::getHighestAltitude() +{ + CloudsDefinition* definition = parent->getScenery()->getClouds(); + double highest = 0.0; + + int n = definition->count(); + double low, high; + for (int i = 0; i < n; i++) + { + BaseCloudsModel* layer_model = getLayerModel(i); + layer_model->getAltitudeRange(&low, &high); + if (high > highest) + { + highest = high; + } + } + + return highest; +} diff --git a/src/render/software/CloudsRenderer.h b/src/render/software/CloudsRenderer.h index be1686a..acb28c1 100644 --- a/src/render/software/CloudsRenderer.h +++ b/src/render/software/CloudsRenderer.h @@ -67,6 +67,11 @@ public: * Return true if the light was altered. */ virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override; + + /** + * Get the highest altitude of all layers. + */ + double getHighestAltitude(); private: double quality; diff --git a/src/render/software/GodRaysResult.cpp b/src/render/software/GodRaysResult.cpp new file mode 100644 index 0000000..5a629ae --- /dev/null +++ b/src/render/software/GodRaysResult.cpp @@ -0,0 +1,38 @@ +#include "GodRaysResult.h" + +GodRaysResult::GodRaysResult(double inside_length, double full_length): + inside_length(inside_length), full_length(full_length) +{ +} + +Color GodRaysResult::apply(const Color &raw, const Color &atmosphered, const GodRaysParams ¶ms) +{ + if (inside_length == 0.0) + { + return raw; + } + else if (inside_length < full_length) + { + double diff = full_length - inside_length; + double factor = 1.0 - params.penetration * diff; + double minimum = params.resistance; + double complement = 1.0 - minimum; + if (factor < minimum) + { + factor = minimum; + } + else + { + factor = pow((factor - minimum) / complement, params.boost) * complement + minimum; + } + + return Color(raw.r + (atmosphered.r - raw.r) * factor, + raw.g + (atmosphered.g - raw.g) * factor, + raw.b + (atmosphered.b - raw.b) * factor, + atmosphered.a); + } + else + { + return atmosphered; + } +} diff --git a/src/render/software/GodRaysResult.h b/src/render/software/GodRaysResult.h new file mode 100644 index 0000000..15102f4 --- /dev/null +++ b/src/render/software/GodRaysResult.h @@ -0,0 +1,40 @@ +#ifndef GODRAYSRESULT_H +#define GODRAYSRESULT_H + +#include "software_global.h" + +#include "Color.h" + +namespace paysages { +namespace software { + +/** + * Result of god rays computation. + */ +class SOFTWARESHARED_EXPORT GodRaysResult +{ +public: + typedef struct { + double penetration; + double resistance; + double boost; + } GodRaysParams; + +public: + GodRaysResult() = default; + GodRaysResult(double inside_length, double full_length); + + /** + * Apply the result on a base color. + */ + Color apply(const Color &raw, const Color &atmosphered, const GodRaysParams ¶ms); + +private: + double inside_length; + double full_length; +}; + +} +} + +#endif // GODRAYSRESULT_H diff --git a/src/render/software/GodRaysSampler.cpp b/src/render/software/GodRaysSampler.cpp new file mode 100644 index 0000000..39c6e67 --- /dev/null +++ b/src/render/software/GodRaysSampler.cpp @@ -0,0 +1,220 @@ +#include "GodRaysSampler.h" + +#include "GodRaysDefinition.h" +#include "AtmosphereDefinition.h" +#include "SoftwareRenderer.h" +#include "Vector3.h" +#include "SpaceSegment.h" +#include "GodRaysResult.h" +#include "LightingManager.h" +#include "FloatNode.h" +#include "LightStatus.h" +#include "Scenery.h" +#include "TerrainDefinition.h" +#include "CloudsRenderer.h" +#include "Interpolation.h" +#include + +GodRaysSampler::GodRaysSampler() +{ + enabled = true; + bounds = new SpaceSegment(); + definition = new GodRaysDefinition(NULL); + camera_location = new Vector3(0, 0, 0); + lighting = NULL; + low_altitude = -1.0; + high_altitude = 1.0; + sampling_step = 1.0; + max_length = 1.0; + data = new double[1]; + setQuality(0.5); +} + +GodRaysSampler::~GodRaysSampler() +{ + delete definition; + delete bounds; + delete camera_location; + delete[] data; +} + +void GodRaysSampler::prepare(SoftwareRenderer *renderer) +{ + setCameraLocation(renderer->getCameraLocation(VECTOR_ZERO)); + setLighting(renderer->getLightingManager()); + setAltitudes(renderer->getScenery()->getTerrain()->getHeightInfo().min_height, renderer->getCloudsRenderer()->getHighestAltitude()); + renderer->getScenery()->getAtmosphere()->childGodRays()->copy(definition); + reset(); +} + +void GodRaysSampler::reset() +{ + *bounds = SpaceSegment(Vector3(camera_location->x - max_length, low_altitude, camera_location->z - max_length), + Vector3(camera_location->x + max_length, high_altitude, camera_location->z + max_length)); + samples_x = round(bounds->getXDiff() / sampling_step) + 1; + samples_y = round(bounds->getYDiff() / sampling_step) + 1; + samples_z = round(bounds->getZDiff() / sampling_step) + 1; + + long n = samples_x * samples_y * samples_z; + delete[] data; + data = new double[n]; + for (long i = 0; i < n; i++) + { + data[i] = -1.0; + } +} + +void GodRaysSampler::setEnabled(bool enabled) +{ + this->enabled = enabled; +} + +void GodRaysSampler::setLighting(LightingManager *manager) +{ + this->lighting = manager; +} + +void GodRaysSampler::setQuality(double factor) +{ + setQuality(5.0 / (factor * 8.0 + 1.0), 100.0, 5.0 / (factor * 80.0 + 1.0)); + reset(); +} + +void GodRaysSampler::setQuality(double sampling_step, double max_length, double walk_step) +{ + this->sampling_step = sampling_step; + this->max_length = max_length; + this->walk_step = walk_step; +} + +void GodRaysSampler::setCameraLocation(const Vector3 &location) +{ + *camera_location = location; +} + +void GodRaysSampler::setAltitudes(double low, double high) +{ + low_altitude = low; + high_altitude = high; +} + +void GodRaysSampler::getSamples(int *x, int *y, int *z) const +{ + *x = samples_x; + *y = samples_y; + *z = samples_z; +} + +Color GodRaysSampler::getRawLight(const Vector3 &location, bool filtered) const +{ + if (lighting) + { + LightStatus status(lighting, location, *camera_location, filtered); + lighting->fillStatus(status, location); + return status.getSum(); + } + else + { + return COLOR_TRANSPARENT; + } +} + +double GodRaysSampler::getCachedLight(const Vector3 &location) +{ + double x = location.x - bounds->getStart().x; + double y = location.y - bounds->getStart().y; + double z = location.z - bounds->getStart().z; + + int ix = (int)floor(x / sampling_step); + int iy = (int)floor(y / sampling_step); + int iz = (int)floor(z / sampling_step); + + // Check cache limits + if (ix < 0 || ix >= samples_x - 1 || iy < 0 || iy >= samples_y - 1 || iz < 0 || iz >= samples_z - 1) + { + return 1.0; + } + + // Hit cache + double p[8] = { + getCache(ix, iy, iz), + getCache(ix + 1, iy, iz), + getCache(ix + 1, iy + 1, iz), + getCache(ix, iy + 1, iz), + getCache(ix, iy, iz + 1), + getCache(ix + 1, iy, iz + 1), + getCache(ix + 1, iy + 1, iz + 1), + getCache(ix, iy + 1, iz + 1) + }; + return Interpolation::trilinear(p, + (x - sampling_step * double(ix)) / sampling_step, + (y - sampling_step * double(iy)) / sampling_step, + (z - sampling_step * double(iz)) / sampling_step); +} + +GodRaysResult GodRaysSampler::getResult(const SpaceSegment &segment) +{ + Vector3 step = segment.getEnd().sub(segment.getStart()); + double max_length = step.getNorm(); + step = step.normalize().scale(walk_step); + Vector3 walker = segment.getStart(); + double travelled = 0.0; + double inside = 0.0; + + if (max_length > this->max_length) + { + max_length = this->max_length; + } + + while (travelled < max_length) + { + double light = getCachedLight(walker); + + inside += light * walk_step; + + walker = walker.add(step); + travelled += walk_step; + } + + return GodRaysResult(inside, travelled); +} + +Color GodRaysSampler::apply(const Color &raw, const Color &atmosphered, const Vector3 &location) +{ + if (enabled) + { + GodRaysResult result = getResult(SpaceSegment(*camera_location, location)); + + GodRaysResult::GodRaysParams params; + params.penetration = definition->propPenetration()->getValue(); + params.resistance = definition->propResistance()->getValue(); + params.boost = definition->propBoost()->getValue(); + + return result.apply(raw, atmosphered, params); + } + else + { + return atmosphered; + } +} + +inline double GodRaysSampler::getCache(int x, int y, int z) +{ + double *cache = data + z * samples_x * samples_y + y * samples_x + x; + if (*cache < 0.0) + { + Vector3 location = Vector3(bounds->getStart().x + sampling_step * (double)x, + bounds->getStart().y + sampling_step * (double)y, + bounds->getStart().z + sampling_step * (double)z); + double unfiltered_power = getRawLight(location, false).getPower(); + if (unfiltered_power == 0.0) + { + *cache = 1.0; + } + else + { + *cache = getRawLight(location, true).getPower() / unfiltered_power; + } + } + return *cache; +} diff --git a/src/render/software/GodRaysSampler.h b/src/render/software/GodRaysSampler.h new file mode 100644 index 0000000..27efc67 --- /dev/null +++ b/src/render/software/GodRaysSampler.h @@ -0,0 +1,124 @@ +#ifndef GODRAYSSAMPLER_H +#define GODRAYSSAMPLER_H + +#include "software_global.h" + +namespace paysages { +namespace software { + +/** + * 3D sampler for "god rays". + */ +class SOFTWARESHARED_EXPORT GodRaysSampler +{ +public: + GodRaysSampler(); + ~GodRaysSampler(); + + inline const SpaceSegment &getBounds() const {return *bounds;} + inline double getSamplingStep() const {return sampling_step;} + inline double getMaxLength() const {return max_length;} + inline double getWalkStep() const {return walk_step;} + + /** + * Prepare the sampler from a renderer. + */ + void prepare(SoftwareRenderer *renderer); + + /** + * Reset the sampler cache. + * + * This needs to be called after all setXXX method have been called. + */ + void reset(); + + /** + * Enable or disable the god rays effect. + */ + void setEnabled(bool enabled); + + /** + * Set the lighting manager to use for raw sampling. + */ + void setLighting(LightingManager *manager); + + /** + * Set the overall quality factor (0.0-1.0). + */ + void setQuality(double factor); + + /** + * Set the quality indicators. + */ + void setQuality(double sampling_step, double max_length, double walk_step); + + /** + * Set the camera location. + * + * This will fix the limits of sampling data, around the camera location. + */ + void setCameraLocation(const Vector3 &location); + + /** + * Set the altitude boundaries. + */ + void setAltitudes(double low, double high); + + /** + * Get the number of samples in each dimension. + */ + void getSamples(int *x, int *y, int *z) const; + + /** + * Get the raw light at a location (without using the cached sampling). + */ + Color getRawLight(const Vector3 &location, bool filtered) const; + + /** + * Get the lighting factor at a given point, using (and filling) the cached sampling. + */ + double getCachedLight(const Vector3 &location); + + /** + * Get the god rays effect on a space segment. + */ + GodRaysResult getResult(const SpaceSegment &segment); + + /** + * Apply the god rays effect to a location. + * + * *raw* is the shaded color, without atmospheric effects. + * *atmosphered* is the shaded color, with atmospheric effects applied. + */ + Color apply(const Color &raw, const Color &atmosphered, const Vector3 &location); + +private: + double getCache(int x, int y, int z); + +private: + bool enabled; + SpaceSegment *bounds; + + GodRaysDefinition *definition; + + double sampling_step; + double max_length; + double walk_step; + + int samples_x; + int samples_y; + int samples_z; + + double low_altitude; + double high_altitude; + Vector3 *camera_location; + + LightingManager *lighting; + + double *data; +}; + +} +} + +#endif // GODRAYSSAMPLER_H diff --git a/src/render/software/LightComponent.cpp b/src/render/software/LightComponent.cpp index bc21dca..ac00e66 100644 --- a/src/render/software/LightComponent.cpp +++ b/src/render/software/LightComponent.cpp @@ -1,5 +1,6 @@ #include "LightComponent.h" -/*LightComponent::LightComponent() +LightComponent::LightComponent(const Color &color, const Vector3 &direction, double reflection, bool altered): + color(color), direction(direction), reflection(reflection), altered(altered) { -}*/ +} diff --git a/src/render/software/LightComponent.h b/src/render/software/LightComponent.h index 1c18663..4a0140b 100644 --- a/src/render/software/LightComponent.h +++ b/src/render/software/LightComponent.h @@ -17,7 +17,8 @@ namespace software { class SOFTWARESHARED_EXPORT LightComponent { public: - //LightComponent(); + LightComponent() = default; + LightComponent(const Color &color, const Vector3 &direction, double reflection = 0.0, bool altered = true); Color color; // Light power Vector3 direction; // Direction the light is travelling diff --git a/src/render/software/LightStatus.cpp b/src/render/software/LightStatus.cpp index 48373a5..31555d1 100644 --- a/src/render/software/LightStatus.cpp +++ b/src/render/software/LightStatus.cpp @@ -1,32 +1,41 @@ #include "LightStatus.h" #include "LightingManager.h" +#include "LightComponent.h" #include "Color.h" #include "SurfaceMaterial.h" -LightStatus::LightStatus(LightingManager *manager, const Vector3 &location, const Vector3 &eye) +LightStatus::LightStatus(LightingManager *manager, const Vector3 &location, const Vector3 &eye, bool filtered) { this->max_power = 0.0; this->manager = manager; this->location = location; this->eye = eye; + this->filtered = filtered; } void LightStatus::pushComponent(LightComponent component) { - double power = component.color.getPower(); - if (component.altered && (power < max_power * 0.05 || power < 0.001)) + if (filtered) { - // Exclude filtered lights that are owerpowered by a previous one - return; - } - - if (manager->alterLight(component, location)) - { - if (power > max_power) + double power = component.color.getPower(); + if (component.altered && (power < max_power * 0.05 || power < 0.001)) { - max_power = power; + // Exclude filtered lights that are owerpowered by a previous one + return; } + + if (manager->alterLight(component, location)) + { + if (power > max_power) + { + max_power = power; + } + components.push_back(component); + } + } + else + { components.push_back(component); } } @@ -43,3 +52,15 @@ Color LightStatus::apply(const Vector3 &normal, const SurfaceMaterial &material) final.a = material.base->a; return final; } + +Color LightStatus::getSum() const +{ + Color final = COLOR_BLACK; + + for (auto component: components) + { + final = final.add(component.color); + } + + return final; +} diff --git a/src/render/software/LightStatus.h b/src/render/software/LightStatus.h index 947ee8a..0f0d7c2 100644 --- a/src/render/software/LightStatus.h +++ b/src/render/software/LightStatus.h @@ -16,7 +16,7 @@ namespace software { class SOFTWARESHARED_EXPORT LightStatus { public: - LightStatus(LightingManager *manager, const Vector3 &location, const Vector3 &eye); + LightStatus(LightingManager *manager, const Vector3 &location, const Vector3 &eye, bool filtered=true); inline Vector3 getLocation() const {return location;} @@ -24,11 +24,17 @@ public: Color apply(const Vector3 &normal, const SurfaceMaterial &material); + /** + * Return the sum of all received lights. + */ + Color getSum() const; + private: double max_power; LightingManager* manager; Vector3 location; Vector3 eye; + bool filtered; std::vector components; }; diff --git a/src/render/software/LightingManager.cpp b/src/render/software/LightingManager.cpp index e352489..bb0bb96 100644 --- a/src/render/software/LightingManager.cpp +++ b/src/render/software/LightingManager.cpp @@ -37,6 +37,11 @@ void LightingManager::addStaticLight(const LightComponent &light) static_lights.push_back(light); } +void LightingManager::clearSources() +{ + sources.clear(); +} + void LightingManager::registerSource(LightSource *source) { if (std::find(sources.begin(), sources.end(), source) == sources.end()) @@ -47,7 +52,15 @@ void LightingManager::registerSource(LightSource *source) void LightingManager::unregisterSource(LightSource *source) { - sources.erase(std::find(sources.begin(), sources.end(), source)); + if (std::find(sources.begin(), sources.end(), source) != sources.end()) + { + sources.erase(std::find(sources.begin(), sources.end(), source)); + } +} + +void LightingManager::clearFilters() +{ + filters.clear(); } void LightingManager::registerFilter(LightFilter* filter) @@ -60,7 +73,10 @@ void LightingManager::registerFilter(LightFilter* filter) void LightingManager::unregisterFilter(LightFilter *filter) { - filters.erase(std::find(filters.begin(), filters.end(), filter)); + if (std::find(filters.begin(), filters.end(), filter) != filters.end()) + { + filters.erase(std::find(filters.begin(), filters.end(), filter)); + } } bool LightingManager::alterLight(LightComponent &component, const Vector3 &location) @@ -168,10 +184,8 @@ Color LightingManager::applyFinalComponent(const LightComponent &component, cons return result; } -Color LightingManager::apply(const Vector3 &eye, const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material) +void LightingManager::fillStatus(LightStatus &status, const Vector3 &location) const { - LightStatus status(this, location, eye); - for (auto &light: static_lights) { status.pushComponent(light); @@ -187,6 +201,11 @@ Color LightingManager::apply(const Vector3 &eye, const Vector3 &location, const } } } +} +Color LightingManager::apply(const Vector3 &eye, const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material) +{ + LightStatus status(this, location, eye); + fillStatus(status, location); return status.apply(normal, material); } diff --git a/src/render/software/LightingManager.h b/src/render/software/LightingManager.h index 027d032..f4c2544 100644 --- a/src/render/software/LightingManager.h +++ b/src/render/software/LightingManager.h @@ -35,6 +35,11 @@ public: */ void addStaticLight(const LightComponent &light); + /** + * Remove all registered sources. + */ + void clearSources(); + /** * Register a source of dynamic lighting. */ @@ -45,6 +50,11 @@ public: */ void unregisterSource(LightSource *source); + /** + * Remove all registered filters. + */ + void clearFilters(); + /** * Register a filter that will receive all alterable lights. */ @@ -72,6 +82,11 @@ public: */ Color applyFinalComponent(const LightComponent &component, const Vector3 &eye, const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material); + /** + * Compute the light status at a given location. + */ + void fillStatus(LightStatus &status, const Vector3 &location) const; + /** * Apply lighting to a surface at a given location. */ diff --git a/src/render/software/NightSky.cpp b/src/render/software/NightSky.cpp index 6cb4bd9..d70923e 100644 --- a/src/render/software/NightSky.cpp +++ b/src/render/software/NightSky.cpp @@ -95,7 +95,7 @@ const Color NightSky::getColor(double altitude, const Vector3 &direction) } -bool NightSky::getLightsAt(std::vector &result, const Vector3 &location) const +bool NightSky::getLightsAt(std::vector &result, const Vector3 &) const { LightComponent moon, sky; diff --git a/src/render/software/SkyRasterizer.cpp b/src/render/software/SkyRasterizer.cpp index c2d207a..0386cb6 100644 --- a/src/render/software/SkyRasterizer.cpp +++ b/src/render/software/SkyRasterizer.cpp @@ -9,6 +9,7 @@ #include "Rasterizer.h" #include "CanvasFragment.h" #include "RenderProgress.h" +#include "GodRaysSampler.h" #define SPHERE_SIZE 20000.0 @@ -68,7 +69,7 @@ void SkyRasterizer::rasterizeToCanvas(CanvasPortion* canvas) direction.z = SPHERE_SIZE * sin(current_i) * cos(current_j + step_j); vertex4 = camera_location.add(direction); - /* TODO Triangles at poles */ + // TODO Triangles at poles pushQuad(canvas, vertex1, vertex4, vertex3, vertex2); } progress->add(res_i); @@ -84,7 +85,7 @@ Color SkyRasterizer::shadeFragment(const CanvasFragment &fragment) const camera_location = renderer->getCameraLocation(location); direction = location.sub(camera_location); - /* TODO Don't compute result->color if it's fully covered by clouds */ + // TODO Don't compute sky color if it's fully covered by clouds result = renderer->getAtmosphereRenderer()->getSkyColor(direction.normalize()).final; result = renderer->getCloudsRenderer()->getColor(camera_location, camera_location.add(direction.scale(10.0)), result); diff --git a/src/render/software/SoftwareRenderer.cpp b/src/render/software/SoftwareRenderer.cpp index d8509cc..6289511 100644 --- a/src/render/software/SoftwareRenderer.cpp +++ b/src/render/software/SoftwareRenderer.cpp @@ -17,6 +17,8 @@ #include "NightSky.h" #include "LightStatus.h" #include "LightingManager.h" +#include "GodRaysSampler.h" +#include "GodRaysResult.h" #include "System.h" #include "Thread.h" @@ -37,6 +39,7 @@ SoftwareRenderer::SoftwareRenderer(Scenery* scenery): fluid_medium = new FluidMediumManager(this); lighting = new LightingManager(); + godrays = new GodRaysSampler(); lighting->registerFilter(water_renderer); lighting->registerFilter(terrain_renderer); @@ -52,6 +55,7 @@ SoftwareRenderer::~SoftwareRenderer() delete fluid_medium; delete lighting; + delete godrays; delete nightsky_renderer; @@ -88,6 +92,7 @@ void SoftwareRenderer::prepare() nightsky_renderer->update(); // Prepare global tools + godrays->prepare(this); fluid_medium->clearMedia(); //fluid_medium->registerMedium(water_renderer); } @@ -96,6 +101,7 @@ void SoftwareRenderer::setQuality(double quality) { terrain_renderer->setQuality(quality); clouds_renderer->setQuality(quality); + godrays->setQuality(quality); // TEMP compat with old code render_quality = (int)(quality * 9.0) + 1; diff --git a/src/render/software/SoftwareRenderer.h b/src/render/software/SoftwareRenderer.h index 5ac5a4d..b8fe4ce 100644 --- a/src/render/software/SoftwareRenderer.h +++ b/src/render/software/SoftwareRenderer.h @@ -57,6 +57,7 @@ public: inline FluidMediumManager* getFluidMediumManager() const {return fluid_medium;} inline LightingManager* getLightingManager() const {return lighting;} + inline GodRaysSampler* getGodRaysSampler() const {return godrays;} virtual Color applyLightingToSurface(const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material); virtual Color applyMediumTraversal(Vector3 location, Color color); @@ -67,6 +68,7 @@ private: FluidMediumManager* fluid_medium; LightingManager* lighting; + GodRaysSampler* godrays; BaseAtmosphereRenderer* atmosphere_renderer; CloudsRenderer* clouds_renderer; diff --git a/src/render/software/software.pro b/src/render/software/software.pro index 9198949..dc427d9 100644 --- a/src/render/software/software.pro +++ b/src/render/software/software.pro @@ -52,7 +52,9 @@ SOURCES += SoftwareRenderer.cpp \ clouds/CloudModelCirrus.cpp \ clouds/CloudModelCumuloNimbus.cpp \ RenderProgress.cpp \ - LightSource.cpp + LightSource.cpp \ + GodRaysSampler.cpp \ + GodRaysResult.cpp HEADERS += SoftwareRenderer.h\ software_global.h \ @@ -94,7 +96,9 @@ HEADERS += SoftwareRenderer.h\ clouds/CloudModelCirrus.h \ clouds/CloudModelCumuloNimbus.h \ RenderProgress.h \ - LightSource.h + LightSource.h \ + GodRaysSampler.h \ + GodRaysResult.h unix:!symbian { maemo5 { diff --git a/src/render/software/software_global.h b/src/render/software/software_global.h index 79c82b1..f80e348 100644 --- a/src/render/software/software_global.h +++ b/src/render/software/software_global.h @@ -49,6 +49,9 @@ namespace software { class TerrainRayWalker; + class GodRaysSampler; + class GodRaysResult; + class Canvas; class CanvasPortion; class CanvasPixel; diff --git a/src/tests/GodRaysSampler_Test.cpp b/src/tests/GodRaysSampler_Test.cpp new file mode 100644 index 0000000..f4a0c97 --- /dev/null +++ b/src/tests/GodRaysSampler_Test.cpp @@ -0,0 +1,149 @@ +#include "BaseTestCase.h" +#include "GodRaysSampler.h" + +#include "SpaceSegment.h" +#include "Vector3.h" +#include "LightingManager.h" +#include "LightSource.h" +#include "LightFilter.h" +#include "LightComponent.h" +#include "Color.h" + +TEST(GodRaysSampler, getBounds) +{ + GodRaysSampler sampler; + + sampler.setAltitudes(-5.0, 300.0); + sampler.setCameraLocation(Vector3(100.0, 0.0, 8000.0)); + sampler.setQuality(10.0, 500.0, 1.0); + sampler.reset(); + + SpaceSegment bounds = sampler.getBounds(); + + EXPECT_VECTOR3_COORDS(bounds.getStart(), -400.0, -5.0, 7500.0); + EXPECT_VECTOR3_COORDS(bounds.getEnd(), 600.0, 300.0, 8500.0); +} + +TEST(GodRaysSampler, getSamples) +{ + GodRaysSampler sampler; + + sampler.setAltitudes(-50.0, 100.0); + sampler.setQuality(10.0, 100.0, 1.0); + sampler.reset(); + + int x, y, z; + sampler.getSamples(&x, &y, &z); + + EXPECT_EQ(21, x); + EXPECT_EQ(16, y); + EXPECT_EQ(21, z); +} + +TEST(GodRaysSampler, setQuality) +{ + GodRaysSampler sampler; + + sampler.setQuality(0.0); + EXPECT_DOUBLE_EQ(5.0, sampler.getSamplingStep()); + EXPECT_DOUBLE_EQ(100.0, sampler.getMaxLength()); + EXPECT_DOUBLE_EQ(5.0, sampler.getWalkStep()); + + sampler.setQuality(0.5); + EXPECT_DOUBLE_EQ(1.0, sampler.getSamplingStep()); + EXPECT_DOUBLE_EQ(100.0, sampler.getMaxLength()); + EXPECT_DOUBLE_EQ(0.12195121951, sampler.getWalkStep()); + + sampler.setQuality(1.0); + EXPECT_DOUBLE_EQ(0.55555555555, sampler.getSamplingStep()); + EXPECT_DOUBLE_EQ(100.0, sampler.getMaxLength()); + EXPECT_DOUBLE_EQ(0.06172839506, sampler.getWalkStep()); +} + +class GodRayLightSource: public LightSource +{ + virtual bool getLightsAt(std::vector &result, const Vector3 &location) const override + { + LightComponent light; + light.color = Color(fabs(location.x), fabs(location.y), fabs(location.z)); + result.push_back(light); + return true; + } +}; + +TEST(GodRaysSampler, getRawLight) +{ + class TestLightFilter: public LightFilter + { + virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override + { + if (at.x > 0.5) + { + light.color.r = 0.1; + return true; + } + else + { + return true; + } + } + }; + + GodRayLightSource light_source; + TestLightFilter filter; + LightingManager lighting; + lighting.registerSource(&light_source); + lighting.registerFilter(&filter); + + GodRaysSampler sampler; + sampler.setLighting(&lighting); + + EXPECT_COLOR_RGBA(sampler.getRawLight(Vector3(0.0, 0.0, 0.0), false), 0.0, 0.0, 0.0, 1.0); + EXPECT_COLOR_RGBA(sampler.getRawLight(Vector3(1.0, 2.0, -6.0), false), 1.0, 2.0, 6.0, 1.0); + + EXPECT_COLOR_RGBA(sampler.getRawLight(Vector3(0.0, 0.0, 0.0), true), 0.0, 0.0, 0.0, 1.0); + EXPECT_COLOR_RGBA(sampler.getRawLight(Vector3(1.0, 2.0, -6.0), true), 0.1, 2.0, 6.0, 1.0); +} + +TEST(GodRaysSampler, getCachedLight) +{ + class TestLightFilter: public LightFilter + { + virtual bool applyLightFilter(LightComponent &, const Vector3 &at) override + { + return at.x <= 10.0 or at.x >= 50.0; + } + }; + + GodRayLightSource light_source; + TestLightFilter filter; + LightingManager lighting; + lighting.registerSource(&light_source); + lighting.registerFilter(&filter); + + GodRaysSampler sampler; + + sampler.setLighting(&lighting); + sampler.setQuality(5.0, 100.0, 1.0); + sampler.setAltitudes(-10.0, 10.0); + sampler.reset(); + + // outside the sampler, considered full light + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 15.0, 0.0))); + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, -15.0, 0.0))); + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(-120.0, 0.0, 0.0))); + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(120.0, 0.0, 0.0))); + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 0.0, -100.1))); + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 0.0, 100.1))); + + // at sampling point + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 0.0, 0.0))); + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(0.0, 5.0, 10.0))); + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(5.0, 5.0, 10.0))); + EXPECT_DOUBLE_EQ(1.0, sampler.getCachedLight(Vector3(10.0, 5.0, 10.0))); + EXPECT_DOUBLE_EQ(0.0, sampler.getCachedLight(Vector3(15.0, 5.0, 10.0))); + EXPECT_DOUBLE_EQ(0.0, sampler.getCachedLight(Vector3(20.0, 5.0, 10.0))); + + // in between sampling points + EXPECT_DOUBLE_EQ(0.5, sampler.getCachedLight(Vector3(12.5, 5.0, 10.0))); +} diff --git a/src/tests/Interpolation_Test.cpp b/src/tests/Interpolation_Test.cpp new file mode 100644 index 0000000..000a771 --- /dev/null +++ b/src/tests/Interpolation_Test.cpp @@ -0,0 +1,27 @@ +#include "BaseTestCase.h" +#include "Interpolation.h" + +TEST(Interpolation, trilinear) +{ + double p[8] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; + + EXPECT_DOUBLE_EQ(0.0, Interpolation::trilinear(p, 0.0, 0.0, 0.0)); + EXPECT_DOUBLE_EQ(1.0, Interpolation::trilinear(p, 1.0, 0.0, 0.0)); + EXPECT_DOUBLE_EQ(2.0, Interpolation::trilinear(p, 1.0, 1.0, 0.0)); + EXPECT_DOUBLE_EQ(3.0, Interpolation::trilinear(p, 0.0, 1.0, 0.0)); + EXPECT_DOUBLE_EQ(4.0, Interpolation::trilinear(p, 0.0, 0.0, 1.0)); + EXPECT_DOUBLE_EQ(5.0, Interpolation::trilinear(p, 1.0, 0.0, 1.0)); + EXPECT_DOUBLE_EQ(6.0, Interpolation::trilinear(p, 1.0, 1.0, 1.0)); + EXPECT_DOUBLE_EQ(7.0, Interpolation::trilinear(p, 0.0, 1.0, 1.0)); + + EXPECT_DOUBLE_EQ(0.5, Interpolation::trilinear(p, 0.5, 0.0, 0.0)); + EXPECT_DOUBLE_EQ(1.5, Interpolation::trilinear(p, 0.0, 0.5, 0.0)); + EXPECT_DOUBLE_EQ(2.0, Interpolation::trilinear(p, 0.0, 0.0, 0.5)); + + EXPECT_DOUBLE_EQ(1.5, Interpolation::trilinear(p, 0.5, 0.5, 0.0)); + EXPECT_DOUBLE_EQ(3.5, Interpolation::trilinear(p, 0.0, 0.5, 0.5)); + EXPECT_DOUBLE_EQ(2.5, Interpolation::trilinear(p, 0.5, 0.0, 0.5)); + + EXPECT_DOUBLE_EQ(3.5, Interpolation::trilinear(p, 0.5, 0.5, 0.5)); +} + diff --git a/src/tests/tests.pro b/src/tests/tests.pro index 4c49dea..85b15ad 100644 --- a/src/tests/tests.pro +++ b/src/tests/tests.pro @@ -29,7 +29,9 @@ SOURCES += main.cpp \ ColorHSL_Test.cpp \ RenderProgress_Test.cpp \ IntNode_Test.cpp \ - LightingManager_Test.cpp + LightingManager_Test.cpp \ + GodRaysSampler_Test.cpp \ + Interpolation_Test.cpp HEADERS += \ BaseTestCase.h