From 3b27d3be3ebb93089d64aaf17616da63a3de207f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Sun, 10 Jan 2016 14:27:32 +0100 Subject: [PATCH] Refactored textures renderer --- src/basics/Vector3.cpp | 18 +++ src/basics/Vector3.h | 16 ++ src/render/opengl/OpenGLTerrainChunk.cpp | 3 +- src/render/software/SoftwareRenderer.cpp | 3 +- src/render/software/TerrainRasterizer.cpp | 43 +++-- src/render/software/TerrainRasterizer.h | 4 +- src/render/software/TerrainRayWalker.cpp | 10 +- src/render/software/TerrainRenderer.cpp | 104 ++++++------ src/render/software/TerrainRenderer.h | 13 +- src/render/software/TexturesRenderer.cpp | 174 ++++++++++----------- src/render/software/TexturesRenderer.h | 75 +++++---- src/render/software/VegetationRenderer.cpp | 7 +- src/tests/TestToolNoise.h | 25 +++ src/tests/TexturesRenderer_Test.cpp | 42 +++++ src/tests/Vector3_Test.cpp | 8 + 15 files changed, 342 insertions(+), 203 deletions(-) create mode 100644 src/tests/TestToolNoise.h create mode 100644 src/tests/TexturesRenderer_Test.cpp diff --git a/src/basics/Vector3.cpp b/src/basics/Vector3.cpp index bc1e6f2..bde9e05 100644 --- a/src/basics/Vector3.cpp +++ b/src/basics/Vector3.cpp @@ -111,6 +111,24 @@ VectorSpherical Vector3::toSpherical() const { return result; } +Vector3 Vector3::getNormal3(const Vector3 &posx, const Vector3 &posy) const { + return posx.sub(*this).crossProduct(posy.sub(*this)).normalize(); +} + +Vector3 Vector3::getNormal5(const Vector3 &posx, const Vector3 &negx, const Vector3 &posy, const Vector3 &negy) const { + Vector3 dnegx = negx.sub(*this); + Vector3 dposy = posy.sub(*this); + Vector3 dposx = posx.sub(*this); + Vector3 dnegy = negy.sub(*this); + + Vector3 normal = dposy.crossProduct(dnegx); + normal = normal.add(dposx.crossProduct(dposy)); + normal = normal.add(dnegy.crossProduct(dposx)); + normal = normal.add(dnegx.crossProduct(dnegy)); + + return normal.normalize(); +} + Vector3 Vector3::midPointTo(const Vector3 &other) const { return Vector3((other.x + x) * 0.5, (other.y + y) * 0.5, (other.z + z) * 0.5); } diff --git a/src/basics/Vector3.h b/src/basics/Vector3.h index 9c5fa8f..88df3ae 100644 --- a/src/basics/Vector3.h +++ b/src/basics/Vector3.h @@ -64,8 +64,24 @@ class BASICSSHARED_EXPORT Vector3 { */ Vector3 midPointTo(const Vector3 &other) const; + /** + * Get the spherical coordinates corresponding to this vector. + */ VectorSpherical toSpherical() const; + /** + * Get a normal vector, using "posx-this" as X axis and "posy-this" as Y axis, to produce the normal along the Z + * axis. + */ + Vector3 getNormal3(const Vector3 &posx, const Vector3 &posy) const; + + /** + * Same as "getNormal3", but using also negative axis for more precision. + * + * As the 4 vectors used may not be in the same place, an average is done. + */ + Vector3 getNormal5(const Vector3 &posx, const Vector3 &negx, const Vector3 &posy, const Vector3 &negy) const; + /** * Produce a random vector in a sphere domain. * diff --git a/src/render/opengl/OpenGLTerrainChunk.cpp b/src/render/opengl/OpenGLTerrainChunk.cpp index 5ace15a..6d10d0a 100644 --- a/src/render/opengl/OpenGLTerrainChunk.cpp +++ b/src/render/opengl/OpenGLTerrainChunk.cpp @@ -9,6 +9,7 @@ #include "ColorProfile.h" #include "CameraDefinition.h" #include "OpenGLRenderer.h" +#include "TerrainRenderer.h" #include "TexturesRenderer.h" #include "Scenery.h" #include "TerrainDefinition.h" @@ -97,7 +98,7 @@ bool OpenGLTerrainChunk::maintain() { if (_texture_current_size <= 1 || i % 2 != 0 || j % 2 != 0) { double x = _startx + factor * to_double(i); double z = _startz + factor * to_double(j); - Color color = _renderer->getTexturesRenderer()->applyToTerrain(x, z, 0.001).final_color; + Color color = _renderer->getTerrainRenderer()->getFinalColor(x, z, 0.001); new_image->setPixel(i, j, Color(color.r * 0.2, color.g * 0.2, color.b * 0.2).normalized().to32BitRGBA()); } } diff --git a/src/render/software/SoftwareRenderer.cpp b/src/render/software/SoftwareRenderer.cpp index 5bef70a..bb6f0a0 100644 --- a/src/render/software/SoftwareRenderer.cpp +++ b/src/render/software/SoftwareRenderer.cpp @@ -32,7 +32,7 @@ SoftwareRenderer::SoftwareRenderer(Scenery *scenery) : scenery(scenery) { atmosphere_renderer = new BaseAtmosphereRenderer(this); clouds_renderer = new CloudsRenderer(this); terrain_renderer = new TerrainRenderer(this); - textures_renderer = new TexturesRenderer(this); + textures_renderer = new TexturesRenderer(); vegetation_renderer = new VegetationRenderer(this); water_renderer = new WaterRenderer(this); @@ -86,7 +86,6 @@ void SoftwareRenderer::prepare() { clouds_renderer->update(); terrain_renderer->update(); water_renderer->update(); - textures_renderer->update(); nightsky_renderer->update(); diff --git a/src/render/software/TerrainRasterizer.cpp b/src/render/software/TerrainRasterizer.cpp index ed8beb4..b602fb4 100644 --- a/src/render/software/TerrainRasterizer.cpp +++ b/src/render/software/TerrainRasterizer.cpp @@ -56,19 +56,19 @@ void TerrainRasterizer::renderQuad(CanvasPortion *canvas, double x, double z, do ov1.x = x; ov1.z = z; - dv1 = renderer->getTerrainRenderer()->getResult(x, z, true, true).location; + dv1 = renderer->getTerrainRenderer()->getDisplaced(x, z, true); ov2.x = x; ov2.z = z + size; - dv2 = renderer->getTerrainRenderer()->getResult(x, z + size, true, true).location; + dv2 = renderer->getTerrainRenderer()->getDisplaced(x, z + size, true); ov3.x = x + size; ov3.z = z + size; - dv3 = renderer->getTerrainRenderer()->getResult(x + size, z + size, true, true).location; + dv3 = renderer->getTerrainRenderer()->getDisplaced(x + size, z + size, true); ov4.x = x + size; ov4.z = z; - dv4 = renderer->getTerrainRenderer()->getResult(x + size, z, true, true).location; + dv4 = renderer->getTerrainRenderer()->getDisplaced(x + size, z, true); if (yoffset != 0.0) { dv1.y += yoffset; @@ -87,11 +87,11 @@ void TerrainRasterizer::setYOffset(double offset) { } void TerrainRasterizer::getChunk(SoftwareRenderer *renderer, TerrainRasterizer::TerrainChunkInfo *chunk, double x, - double z, double size, int displaced) { - chunk->point_nw = renderer->getTerrainRenderer()->getResult(x, z, true, displaced).location; - chunk->point_sw = renderer->getTerrainRenderer()->getResult(x, z + size, true, displaced).location; - chunk->point_se = renderer->getTerrainRenderer()->getResult(x + size, z + size, true, displaced).location; - chunk->point_ne = renderer->getTerrainRenderer()->getResult(x + size, z, true, displaced).location; + double z, double size) { + chunk->point_nw = renderer->getTerrainRenderer()->getResult(x, z, true).location; + chunk->point_sw = renderer->getTerrainRenderer()->getResult(x, z + size, true).location; + chunk->point_se = renderer->getTerrainRenderer()->getResult(x + size, z + size, true).location; + chunk->point_ne = renderer->getTerrainRenderer()->getResult(x + size, z, true).location; if (yoffset != 0.0) { chunk->point_nw.y += yoffset; @@ -100,13 +100,8 @@ void TerrainRasterizer::getChunk(SoftwareRenderer *renderer, TerrainRasterizer:: chunk->point_ne.y += yoffset; } - double displacement_power; - if (displaced) { - displacement_power = 0.0; - } else { - displacement_power = - renderer->getTexturesRenderer()->getMaximalDisplacement(renderer->getScenery()->getTextures()); - } + double displacement_power = + renderer->getTexturesRenderer()->getMaximalDisplacement(renderer->getScenery()->getTextures()); BoundingBox box; if (displacement_power > 0.0) { @@ -137,7 +132,7 @@ void TerrainRasterizer::getChunk(SoftwareRenderer *renderer, TerrainRasterizer:: } } -int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced) { +int TerrainRasterizer::performTessellation(CanvasPortion *canvas) { TerrainChunkInfo chunk; int chunk_factor, chunk_count, i, result; Vector3 cam = renderer->getCameraLocation(VECTOR_ZERO); @@ -156,7 +151,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced while (radius_int < 20000.0) { for (i = 0; i < chunk_count - 1; i++) { - getChunk(renderer, &chunk, cx - radius_ext + chunk_size * i, cz - radius_ext, chunk_size, displaced); + getChunk(renderer, &chunk, cx - radius_ext + chunk_size * i, cz - radius_ext, chunk_size); if (chunk.detail_hint > 0) { result += chunk.detail_hint * chunk.detail_hint; if (canvas) { @@ -167,7 +162,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced return result; } - getChunk(renderer, &chunk, cx + radius_int, cz - radius_ext + chunk_size * i, chunk_size, displaced); + getChunk(renderer, &chunk, cx + radius_int, cz - radius_ext + chunk_size * i, chunk_size); if (chunk.detail_hint > 0) { result += chunk.detail_hint * chunk.detail_hint; if (canvas) { @@ -178,7 +173,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced return result; } - getChunk(renderer, &chunk, cx + radius_int - chunk_size * i, cz + radius_int, chunk_size, displaced); + getChunk(renderer, &chunk, cx + radius_int - chunk_size * i, cz + radius_int, chunk_size); if (chunk.detail_hint > 0) { result += chunk.detail_hint * chunk.detail_hint; if (canvas) { @@ -189,7 +184,7 @@ int TerrainRasterizer::performTessellation(CanvasPortion *canvas, bool displaced return result; } - getChunk(renderer, &chunk, cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size, displaced); + getChunk(renderer, &chunk, cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size); if (chunk.detail_hint > 0) { result += chunk.detail_hint * chunk.detail_hint; if (canvas) { @@ -220,15 +215,15 @@ void TerrainRasterizer::processChunk(CanvasPortion *canvas, TerrainChunkInfo *ch int TerrainRasterizer::prepareRasterization() { // TODO Chunks could be saved and reused in rasterizeToCanvas - return performTessellation(NULL, false); + return performTessellation(NULL); } void TerrainRasterizer::rasterizeToCanvas(CanvasPortion *canvas) { - performTessellation(canvas, false); + performTessellation(canvas); } Color TerrainRasterizer::shadeFragment(const CanvasFragment &fragment, const CanvasFragment *) const { Vector3 point = fragment.getLocation(); double precision = renderer->getPrecision(_getPoint(renderer, point.x, point.z)); - return renderer->getTerrainRenderer()->getFinalColor(point, precision); + return renderer->getTerrainRenderer()->getFinalColor(point.x, point.z, precision); } diff --git a/src/render/software/TerrainRasterizer.h b/src/render/software/TerrainRasterizer.h index e38af5e..cd40edd 100644 --- a/src/render/software/TerrainRasterizer.h +++ b/src/render/software/TerrainRasterizer.h @@ -59,7 +59,7 @@ class SOFTWARESHARED_EXPORT TerrainRasterizer : public Rasterizer { * * 'canvas' may be NULL to only simulate the tessellation. */ - int performTessellation(CanvasPortion *canvas, bool displaced); + int performTessellation(CanvasPortion *canvas); /** * Tessellate a terrain chunk, pushing the quads in the render area. @@ -69,7 +69,7 @@ class SOFTWARESHARED_EXPORT TerrainRasterizer : public Rasterizer { void renderQuad(CanvasPortion *canvas, double x, double z, double size, double water_height); void getChunk(SoftwareRenderer *renderer, TerrainRasterizer::TerrainChunkInfo *chunk, double x, double z, - double size, int displaced); + double size); private: double yoffset; diff --git a/src/render/software/TerrainRayWalker.cpp b/src/render/software/TerrainRayWalker.cpp index b7ab8a8..7897f7e 100644 --- a/src/render/software/TerrainRayWalker.cpp +++ b/src/render/software/TerrainRayWalker.cpp @@ -50,8 +50,9 @@ static inline Vector3 _getShiftAxis(const Vector3 &direction) { bool TerrainRayWalker::startWalking(const Vector3 &start, Vector3 direction, double escape_angle, TerrainHitResult &result) { - TerrainRenderer *terrain_renderer = renderer->getTerrainRenderer(); - TexturesRenderer *textures_renderer = renderer->getTexturesRenderer(); + auto terrain_renderer = renderer->getTerrainRenderer(); + auto textures_renderer = renderer->getTexturesRenderer(); + auto textures_definition = renderer->getScenery()->getTextures(); TerrainRenderer::TerrainResult terrain_result; Vector3 cursor, displaced; double diff; @@ -75,7 +76,7 @@ bool TerrainRayWalker::startWalking(const Vector3 &start, Vector3 direction, dou cursor = previous_cursor.add(direction.scale(step_length)); // Get the terrain info at end (without textures displacement) - terrain_result = terrain_renderer->getResult(cursor.x, cursor.z, true, false); + terrain_result = terrain_renderer->getResult(cursor.x, cursor.z, true); diff = cursor.y - terrain_result.location.y; // If we are very under the terrain, consider a hit @@ -85,7 +86,8 @@ bool TerrainRayWalker::startWalking(const Vector3 &start, Vector3 direction, dou // If we are close enough to the terrain, apply displacement else if (diff < displacement_base * displacement_safety) { - displaced = textures_renderer->displaceTerrain(terrain_result); + displaced = + textures_renderer->displaceTerrain(textures_definition, terrain_result.location, terrain_result.normal); diff = cursor.y - displaced.y; hit = diff < 0.0; } diff --git a/src/render/software/TerrainRenderer.cpp b/src/render/software/TerrainRenderer.cpp index b524e96..339d4a2 100644 --- a/src/render/software/TerrainRenderer.cpp +++ b/src/render/software/TerrainRenderer.cpp @@ -5,6 +5,7 @@ #include "Scenery.h" #include "TerrainDefinition.h" #include "TexturesRenderer.h" +#include "TexturesDefinition.h" #include "LightComponent.h" #include "TerrainRayWalker.h" #include "RayCastingResult.h" @@ -40,27 +41,7 @@ double TerrainRenderer::getHeight(double x, double z, bool with_painting, bool w return parent->getScenery()->getTerrain()->getInterpolatedHeight(x, z, true, with_painting, water_offset); } -static inline Vector3 _getNormal4(Vector3 center, Vector3 north, Vector3 east, Vector3 south, Vector3 west) { - Vector3 dnorth, deast, dsouth, dwest, normal; - - dnorth = north.sub(center); - deast = east.sub(center); - dsouth = south.sub(center); - dwest = west.sub(center); - - normal = deast.crossProduct(dnorth); - normal = normal.add(dsouth.crossProduct(deast)); - normal = normal.add(dwest.crossProduct(dsouth)); - normal = normal.add(dnorth.crossProduct(dwest)); - - return normal.normalize(); -} - -static inline Vector3 _getNormal2(Vector3 center, Vector3 east, Vector3 south) { - return south.sub(center).crossProduct(east.sub(center)).normalize(); -} - -TerrainRenderer::TerrainResult TerrainRenderer::getResult(double x, double z, bool with_painting, bool with_textures) { +TerrainRenderer::TerrainResult TerrainRenderer::getResult(double x, double z, bool with_painting) { TerrainResult result; double offset = 0.001; @@ -88,43 +69,65 @@ TerrainRenderer::TerrainResult TerrainRenderer::getResult(double x, double z, bo north.z = z - offset; north.y = getHeight(north.x, north.z, with_painting); - result.normal = _getNormal4(center, north, east, south, west); + result.normal = center.getNormal5(south, north, east, west); } else { - result.normal = _getNormal2(center, east, south); + result.normal = center.getNormal3(south, east); } /* Location */ result.location = center; - /* Texture displacement */ - if (with_textures) { - center = parent->getTexturesRenderer()->displaceTerrain(result); - result.location = center; - - /* Recompute normal */ - if (parent->render_quality > 6) { - /* Use 5 points on displaced terrain */ - east = parent->getTexturesRenderer()->displaceTerrain(getResult(east.x, east.z, with_painting, 0)); - south = parent->getTexturesRenderer()->displaceTerrain(getResult(south.x, south.z, with_painting, 0)); - west = parent->getTexturesRenderer()->displaceTerrain(getResult(west.x, west.z, with_painting, 0)); - north = parent->getTexturesRenderer()->displaceTerrain(getResult(north.x, north.z, with_painting, 0)); - - result.normal = _getNormal4(center, north, east, south, west); - } else { - /* Use 3 points on displaced terrain */ - east = parent->getTexturesRenderer()->displaceTerrain(getResult(east.x, east.z, with_painting, 0)); - south = parent->getTexturesRenderer()->displaceTerrain(getResult(south.x, south.z, with_painting, 0)); - - result.normal = _getNormal2(center, east, south); - } - } - return result; } -Color TerrainRenderer::getFinalColor(const Vector3 &location, double precision) { - TexturesRenderer::TexturesResult textures = parent->getTexturesRenderer()->applyToTerrain(location.x, location.z, precision); - return parent->applyMediumTraversal(textures.final_location, textures.final_color); +Vector3 TerrainRenderer::getDisplaced(double x, double z, bool with_painting) { + auto terrain = getResult(x, z, with_painting); + auto textures = parent->getScenery()->getTextures(); + return parent->getTexturesRenderer()->displaceTerrain(textures, terrain.location, terrain.normal); +} + +static inline pair, vector> _getTexturesInfo(TerrainRenderer *terrain_renderer, + TexturesRenderer *textures_renderer, double x, + double z, + TexturesDefinition *textures_definition) { + auto terrain = terrain_renderer->getResult(x, z, true); + auto presence = textures_renderer->getLayersPresence(textures_definition, terrain.location, terrain.normal); + auto displaced = + textures_renderer->getLayersDisplacement(textures_definition, terrain.location, terrain.normal, presence); + return pair, vector>(presence, displaced); +} + +Color TerrainRenderer::getFinalColor(double x, double z, double precision) { + auto textures_renderer = parent->getTexturesRenderer(); + auto textures_definition = parent->getScenery()->getTextures(); + + if (textures_definition->getLayerCount() == 0) { + return COLOR_BLACK; + } else { + auto current = _getTexturesInfo(this, textures_renderer, x, z, textures_definition); + auto top_location = current.second.back(); + + vector normal; + int i = 0; + // TODO Use getNormal5 on high-quality renders + double offset = 0.0001; + auto east = _getTexturesInfo(this, textures_renderer, x + offset, z, textures_definition); + auto south = _getTexturesInfo(this, textures_renderer, x, z + offset, textures_definition); + for (auto layer_presence : current.first) { + if (layer_presence > 0.0) { + normal.push_back(current.second[i].getNormal3(south.second[i], east.second[i])); + } else { + normal.push_back(VECTOR_ZERO); + } + i++; + } + + auto color = textures_renderer->getFinalComposition(textures_definition, parent->getLightingManager(), + current.first, current.second, normal, precision, + parent->getCameraLocation(top_location)); + + return parent->applyMediumTraversal(top_location, color); + } } RayCastingResult TerrainRenderer::castRay(const Vector3 &start, const Vector3 &direction) { @@ -133,7 +136,8 @@ RayCastingResult TerrainRenderer::castRay(const Vector3 &start, const Vector3 &d if (walker_ray->startWalking(start, direction.normalize(), 0.0, walk_result)) { result.hit = true; result.hit_location = walk_result.hit_location; - result.hit_color = getFinalColor(walk_result.hit_location, parent->getPrecision(walk_result.hit_location)); + result.hit_color = getFinalColor(walk_result.hit_location.x, walk_result.hit_location.z, + parent->getPrecision(walk_result.hit_location)); } else { result.hit = false; } diff --git a/src/render/software/TerrainRenderer.h b/src/render/software/TerrainRenderer.h index 22d8a80..a9d1f53 100644 --- a/src/render/software/TerrainRenderer.h +++ b/src/render/software/TerrainRenderer.h @@ -5,8 +5,8 @@ #include "LightFilter.h" +#include #include "Vector3.h" -#include "Color.h" namespace paysages { namespace software { @@ -28,8 +28,8 @@ class SOFTWARESHARED_EXPORT TerrainRenderer : public LightFilter { virtual RayCastingResult castRay(const Vector3 &start, const Vector3 &direction); virtual double getHeight(double x, double z, bool with_painting, bool water_offset = true); - virtual TerrainResult getResult(double x, double z, bool with_painting, bool with_textures); - virtual Color getFinalColor(const Vector3 &location, double precision); + virtual TerrainResult getResult(double x, double z, bool with_painting); + Vector3 getDisplaced(double x, double z, bool with_painting); virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override; /** @@ -37,6 +37,13 @@ class SOFTWARESHARED_EXPORT TerrainRenderer : public LightFilter { */ void estimateMinMaxHeight(double x1, double z1, double x2, double z2, double *ymin, double *ymax); + /** + * Get the final color at a given terrain location. + * + * Textures will be applied (with displacement and detail), and medium traversal will be performed at the location. + */ + virtual Color getFinalColor(double x, double z, double precision); + private: SoftwareRenderer *parent; TerrainRayWalker *walker_ray; diff --git a/src/render/software/TexturesRenderer.cpp b/src/render/software/TexturesRenderer.cpp index fdc42d0..f9b3279 100644 --- a/src/render/software/TexturesRenderer.cpp +++ b/src/render/software/TexturesRenderer.cpp @@ -1,33 +1,33 @@ #include "TexturesRenderer.h" +#include #include #include "Scenery.h" #include "SoftwareRenderer.h" #include "TextureLayerDefinition.h" #include "TexturesDefinition.h" #include "Zone.h" +#include "LightingManager.h" #include "NoiseNode.h" #include "FractalNoise.h" #include "NoiseGenerator.h" -TexturesRenderer::TexturesRenderer(SoftwareRenderer *parent) : parent(parent) { +TexturesRenderer::TexturesRenderer() { + setQualityFactor(0.5); } TexturesRenderer::~TexturesRenderer() { } -void TexturesRenderer::update() { +void TexturesRenderer::setQuality(bool normal5) { + quality_normal5 = normal5; } -/* - * Get the base presence factor of a layer, not accounting for other layers. - */ -double TexturesRenderer::getLayerBasePresence(TextureLayerDefinition *layer, - const TerrainRenderer::TerrainResult &terrain) { - return layer->terrain_zone->getValue(terrain.location, terrain.normal); +void TexturesRenderer::setQualityFactor(double factor) { + setQuality(factor > 0.6); } -double TexturesRenderer::getMaximalDisplacement(TexturesDefinition *textures) { +double TexturesRenderer::getMaximalDisplacement(TexturesDefinition *textures) const { int i, n; double disp = 0.0; n = textures->getLayerCount(); @@ -41,28 +41,8 @@ double TexturesRenderer::getMaximalDisplacement(TexturesDefinition *textures) { return disp; } -static inline Vector3 _getNormal4(Vector3 center, Vector3 north, Vector3 east, Vector3 south, Vector3 west) { - Vector3 dnorth, deast, dsouth, dwest, normal; - - dnorth = north.sub(center); - deast = east.sub(center); - dsouth = south.sub(center); - dwest = west.sub(center); - - normal = deast.crossProduct(dnorth); - normal = normal.add(dsouth.crossProduct(deast)); - normal = normal.add(dwest.crossProduct(dsouth)); - normal = normal.add(dnorth.crossProduct(dwest)); - - return normal.normalize(); -} - -static inline Vector3 _getNormal2(Vector3 center, Vector3 east, Vector3 south) { - return south.sub(center).crossProduct(east.sub(center)).normalize(); -} - -static Vector3 _getDetailNormal(SoftwareRenderer *renderer, Vector3 base_location, Vector3 base_normal, - TextureLayerDefinition *layer, double precision) { +static Vector3 _getDetailNormal(Vector3 base_location, Vector3 base_normal, TextureLayerDefinition *layer, + double precision, bool normal5) { Vector3 result; /* Find guiding vectors in the appoximated local plane */ @@ -90,16 +70,16 @@ static Vector3 _getDetailNormal(SoftwareRenderer *renderer, Vector3 base_locatio south = base_location.add(dy.scale(offset)); south = south.add(base_normal.scale(detail_noise->getTriplanar(detail, south, base_normal))); - if (renderer->render_quality > 6) { + if (normal5) { west = base_location.add(dx.scale(-offset)); west = west.add(base_normal.scale(detail_noise->getTriplanar(detail, west, base_normal))); north = base_location.add(dy.scale(-offset)); north = north.add(base_normal.scale(detail_noise->getTriplanar(detail, north, base_normal))); - result = _getNormal4(center, north, east, south, west); + result = center.getNormal5(south, north, east, west); } else { - result = _getNormal2(center, east, south); + result = center.getNormal3(south, east); } if (result.dotProduct(base_normal) < 0.0) { @@ -108,8 +88,24 @@ static Vector3 _getDetailNormal(SoftwareRenderer *renderer, Vector3 base_locatio return result; } -Vector3 TexturesRenderer::displaceTerrain(const TerrainRenderer::TerrainResult &terrain) { - TexturesDefinition *textures = parent->getScenery()->getTextures(); +static inline double _getLayerPresence(TextureLayerDefinition *layer, const Vector3 &location, const Vector3 &normal) { + return layer->terrain_zone->getValue(location, normal); +} + +static inline double _getLayerDisplacement(TextureLayerDefinition *layer, const Vector3 &location, + const Vector3 &normal, double presence) { + auto noise = layer->propDisplacementNoise()->getGenerator(); + return noise->getTriplanar(0.001, location, normal) * presence; +} + +static inline double _getLayerDisplacement(TextureLayerDefinition *layer, const Vector3 &location, + const Vector3 &normal) { + double presence = _getLayerPresence(layer, location, normal); + return _getLayerDisplacement(layer, location, normal, presence); +} + +Vector3 TexturesRenderer::displaceTerrain(const TexturesDefinition *textures, const Vector3 &location, + const Vector3 &normal) const { double offset = 0.0; int i, n; @@ -118,64 +114,68 @@ Vector3 TexturesRenderer::displaceTerrain(const TerrainRenderer::TerrainResult & TextureLayerDefinition *layer = textures->getTextureLayer(i); if (layer->hasDisplacement()) { - double presence = getLayerBasePresence(layer, terrain); - auto noise = layer->propDisplacementNoise()->getGenerator(); - offset += noise->getTriplanar(0.001, terrain.location, terrain.normal) * presence; + offset += _getLayerDisplacement(layer, location, normal); } } - return terrain.location.add(terrain.normal.normalize().scale(offset)); + return location.add(normal.scale(offset)); } -double TexturesRenderer::getBasePresence(int layer, const TerrainRenderer::TerrainResult &terrain) { - TextureLayerDefinition *layerdef = parent->getScenery()->getTextures()->getTextureLayer(layer); - return getLayerBasePresence(layerdef, terrain); -} - -TexturesRenderer::TexturesResult TexturesRenderer::applyToTerrain(double x, double z, double precision) { - TexturesDefinition *textures = parent->getScenery()->getTextures(); - TexturesResult result; - - // Displacement - // FIXME - TerrainRenderer::TerrainResult raw_terrain = parent->getTerrainRenderer()->getResult(x, z, true, false); - TerrainRenderer::TerrainResult terrain = parent->getTerrainRenderer()->getResult(x, z, true, true); - - // TODO Displaced textures had their presence already computed before, store that result and use it - - // Find presence of each layer +vector TexturesRenderer::getLayersPresence(const TexturesDefinition *textures, const Vector3 &location, + const Vector3 &normal) const { int n = textures->getLayerCount(); - int start = 0; + vector result; for (int i = 0; i < n; i++) { - TexturesLayerResult &layer = result.layers[i]; - - layer.definition = textures->getTextureLayer(i); - layer.presence = getBasePresence(i, raw_terrain); - if (layer.presence > 0.9999) { - start = i; - } + TextureLayerDefinition *layer = textures->getTextureLayer(i); + double presence = _getLayerPresence(layer, location, normal); + result.push_back(presence); + } + return result; +} + +vector TexturesRenderer::getLayersDisplacement(const TexturesDefinition *textures, const Vector3 &location, + const Vector3 &normal, const vector &presence) const { + int n = textures->getLayerCount(); + assert(presence.size() == to_size(n)); + vector result; + Vector3 displaced = location; + for (int i = 0; i < n; i++) { + double layer_presence = presence[i]; + if (layer_presence > 0.0) { + TextureLayerDefinition *layer = textures->getTextureLayer(i); + double displacement = _getLayerDisplacement(layer, location, normal, layer_presence); + displaced = displaced.add(normal.scale(displacement * layer_presence)); + } + result.push_back(displaced); + } + return result; +} + +Color TexturesRenderer::getFinalComposition(const TexturesDefinition *textures, LightingManager *lighting, + const vector &presence, const vector &location, + const vector &normal, double precision, const Vector3 &eye) const { + int n = textures->getLayerCount(); + assert(presence.size() == to_size(n)); + assert(location.size() == to_size(n)); + assert(normal.size() == to_size(n)); + Color result = COLOR_BLACK; + // TODO share the same lighting status (no need to recompute shadows) + int i; + for (i = n - 1; i > 0; i--) { + // Start at the top-most covering layer (layers underneath are only important for displacement, not color) + if (presence[i] > 0.99999) { + break; + } + } + for (i = 0; i < n; i++) { + double layer_presence = presence[i]; + if (layer_presence > 0.0) { + TextureLayerDefinition *layer = textures->getTextureLayer(i); + auto detail_normal = _getDetailNormal(location[i], normal[i], layer, precision, quality_normal5); + Color layer_color = lighting->apply(eye, location[i], detail_normal, *layer->material); + layer_color.a *= layer_presence; + result.mask(layer_color); + } } - result.layer_count = n; - - result.base_location = terrain.location; - result.base_normal = terrain.normal; - result.final_location = terrain.location; - result.final_color = COLOR_GREEN; - - // Compute and merge colors of visible layers - for (int i = start; i < n; i++) { - TexturesLayerResult &layer = result.layers[i]; - - if (layer.presence > 0.0) { - Vector3 normal = _getDetailNormal(parent, terrain.location, terrain.normal, layer.definition, precision); - Vector3 location(x, terrain.location.y, z); - layer.color = parent->applyLightingToSurface(location, normal, *layer.definition->material); - layer.color.a = layer.presence; - result.final_color.mask(layer.color); - } else { - layer.color = COLOR_TRANSPARENT; - } - } - return result; } diff --git a/src/render/software/TexturesRenderer.h b/src/render/software/TexturesRenderer.h index 4fcf955..80d2532 100644 --- a/src/render/software/TexturesRenderer.h +++ b/src/render/software/TexturesRenderer.h @@ -3,45 +3,66 @@ #include "software_global.h" -#include "TerrainRenderer.h" - -#define TEXTURES_MAX_LAYERS 50 +#include namespace paysages { namespace software { class SOFTWARESHARED_EXPORT TexturesRenderer { public: - typedef struct { - TextureLayerDefinition *definition; - double presence; - Color color; - } TexturesLayerResult; - - typedef struct { - Vector3 base_location; - Vector3 base_normal; - int layer_count; - TexturesLayerResult layers[TEXTURES_MAX_LAYERS]; - Vector3 final_location; - Color final_color; - } TexturesResult; - - public: - TexturesRenderer(SoftwareRenderer *parent); + TexturesRenderer(); virtual ~TexturesRenderer(); - virtual void update(); + /** + * Set the quality parameters. + * + * "normal5" can be set to true to use balanced 5 points instead of unbalanced 3 points for normal computations. + */ + void setQuality(bool normal5); - virtual double getMaximalDisplacement(TexturesDefinition *textures); - virtual double getLayerBasePresence(TextureLayerDefinition *layer, const TerrainRenderer::TerrainResult &terrain); + /** + * Set an automated quality factor. + */ + void setQualityFactor(double factor); - virtual Vector3 displaceTerrain(const TerrainRenderer::TerrainResult &terrain); - virtual double getBasePresence(int layer, const TerrainRenderer::TerrainResult &terrain); - virtual TexturesResult applyToTerrain(double x, double z, double precision); + /** + * Get the maximal displacement offset all combined textures can make. + */ + double getMaximalDisplacement(TexturesDefinition *textures) const; + + /** + * Get the fully displaced terrain location (applying all textures). + */ + Vector3 displaceTerrain(const TexturesDefinition *textures, const Vector3 &location, const Vector3 &normal) const; + + /** + * Get the presence of each texture layer at a given terrain location. + */ + vector getLayersPresence(const TexturesDefinition *textures, const Vector3 &location, + const Vector3 &normal) const; + + /** + * Get the displaced location of each texture layer at a given terrain location. + * + * 'presence' is the result of 'getLayersPresence'. + */ + vector getLayersDisplacement(const TexturesDefinition *textures, const Vector3 &location, + const Vector3 &normal, const vector &presence) const; + + /** + * Get the final lighted texture composition. + * + * 'presence' is the result of 'getLayersPresence'. + * 'location' is the result of 'getLayersDisplacement'. + * 'normal' is the normal vector (taking only displacement into account, not detail) at each texture's 'location'. + * 'precision' is the level of detail needed for the composition (minimal height of the detail noise). + */ + Color getFinalComposition(const TexturesDefinition *textures, LightingManager *lighting, + const vector &presence, const vector &location, + const vector &normal, double precision, const Vector3 &eye) const; private: - SoftwareRenderer *parent; + bool quality_normal5; }; } } diff --git a/src/render/software/VegetationRenderer.cpp b/src/render/software/VegetationRenderer.cpp index 3f696f7..9fe5095 100644 --- a/src/render/software/VegetationRenderer.cpp +++ b/src/render/software/VegetationRenderer.cpp @@ -55,9 +55,10 @@ RayCastingResult VegetationRenderer::renderInstance(const SpaceSegment &segment, bool only_hit, bool displaced) { if (!displaced) { // Recursive call on displaced instance - const Vector3 &base = instance.getBase(); - TerrainRenderer::TerrainResult terrain = parent->getTerrainRenderer()->getResult(base.x, base.z, true, true); - VegetationInstance displaced_instance = instance.displace(terrain.location, terrain.normal); + auto base = instance.getBase(); + auto terrain = parent->getTerrainRenderer()->getResult(base.x, base.z, true); + auto displaced = parent->getTerrainRenderer()->getDisplaced(base.x, base.z, true); + auto displaced_instance = instance.displace(displaced, terrain.normal); return renderInstance(segment, displaced_instance, only_hit, true); } diff --git a/src/tests/TestToolNoise.h b/src/tests/TestToolNoise.h new file mode 100644 index 0000000..4307d20 --- /dev/null +++ b/src/tests/TestToolNoise.h @@ -0,0 +1,25 @@ +#ifndef TESTTOOLNOISE_H +#define TESTTOOLNOISE_H + +#include "FractalNoise.h" + +namespace { +/** + * Fractal noise that produces the same value anywhere. + */ +class ConstantFractalNoise : public FractalNoise { + public: + ConstantFractalNoise(double value) : value(value) { + // The noise will yield its value at first iteration, then its height will collapse to 0 + setScaling(1.0, 0.0); + } + virtual double getBase3d(double, double, double) const { + return value; + } + + private: + double value; +}; +} + +#endif // TESTTOOLNOISE_H diff --git a/src/tests/TexturesRenderer_Test.cpp b/src/tests/TexturesRenderer_Test.cpp new file mode 100644 index 0000000..c385da8 --- /dev/null +++ b/src/tests/TexturesRenderer_Test.cpp @@ -0,0 +1,42 @@ +#include "BaseTestCase.h" +#include "TexturesRenderer.h" + +#include "Vector3.h" +#include "TexturesDefinition.h" +#include "TextureLayerDefinition.h" +#include "Zone.h" + +TEST(TexturesRenderer, getLayersPresence) { + TexturesRenderer renderer; + TexturesDefinition textures(NULL); + vector result; + + result = renderer.getLayersPresence(&textures, VECTOR_ZERO, VECTOR_UP); + ASSERT_EQ(0u, result.size()); + + TextureLayerDefinition layer1(NULL, "t1"); + textures.addLayer(layer1); + + result = renderer.getLayersPresence(&textures, VECTOR_ZERO, VECTOR_UP); + ASSERT_EQ(1u, result.size()); + EXPECT_DOUBLE_EQ(1.0, result[0]); + + TextureLayerDefinition layer2(NULL, "t2"); + layer2.terrain_zone->addHeightRangeQuick(0.8, 0.0, 1.0, 2.0, 3.0); + textures.addLayer(layer2); + + result = renderer.getLayersPresence(&textures, VECTOR_ZERO, VECTOR_UP); + ASSERT_EQ(2u, result.size()); + EXPECT_DOUBLE_EQ(1.0, result[0]); + EXPECT_DOUBLE_EQ(0.0, result[1]); + + result = renderer.getLayersPresence(&textures, VECTOR_UP.scale(0.5), VECTOR_UP); + ASSERT_EQ(2u, result.size()); + EXPECT_DOUBLE_EQ(1.0, result[0]); + EXPECT_DOUBLE_EQ(0.4, result[1]); + + result = renderer.getLayersPresence(&textures, VECTOR_UP, VECTOR_UP); + ASSERT_EQ(2u, result.size()); + EXPECT_DOUBLE_EQ(1.0, result[0]); + EXPECT_DOUBLE_EQ(0.8, result[1]); +} diff --git a/src/tests/Vector3_Test.cpp b/src/tests/Vector3_Test.cpp index fbd071a..ee2fe33 100644 --- a/src/tests/Vector3_Test.cpp +++ b/src/tests/Vector3_Test.cpp @@ -20,3 +20,11 @@ TEST(Vector3, randomInSphere) { v = Vector3::randomInSphere(0.5, true); EXPECT_DOUBLE_EQ(v.getNorm(), 0.5); } + +TEST(Vector3, getNormal3) { + EXPECT_VECTOR3_COORDS(VECTOR_ZERO.getNormal3(VECTOR_SOUTH, VECTOR_EAST), 0.0, 1.0, 0.0); +} + +TEST(Vector3, getNormal5) { + EXPECT_VECTOR3_COORDS(VECTOR_ZERO.getNormal5(VECTOR_SOUTH, VECTOR_NORTH, VECTOR_EAST, VECTOR_WEST), 0.0, 1.0, 0.0); +}