Refactored textures renderer
This commit is contained in:
parent
6cf607a557
commit
3b27d3be3e
15 changed files with 342 additions and 203 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<double>, vector<Vector3>> _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<double>, vector<Vector3>>(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<Vector3> 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;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
#include "LightFilter.h"
|
||||
|
||||
#include <vector>
|
||||
#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;
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
#include "TexturesRenderer.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#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<double> TexturesRenderer::getLayersPresence(const TexturesDefinition *textures, const Vector3 &location,
|
||||
const Vector3 &normal) const {
|
||||
int n = textures->getLayerCount();
|
||||
int start = 0;
|
||||
vector<double> 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<Vector3> TexturesRenderer::getLayersDisplacement(const TexturesDefinition *textures, const Vector3 &location,
|
||||
const Vector3 &normal, const vector<double> &presence) const {
|
||||
int n = textures->getLayerCount();
|
||||
assert(presence.size() == to_size(n));
|
||||
vector<Vector3> 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<double> &presence, const vector<Vector3> &location,
|
||||
const vector<Vector3> &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;
|
||||
}
|
||||
|
|
|
@ -3,45 +3,66 @@
|
|||
|
||||
#include "software_global.h"
|
||||
|
||||
#include "TerrainRenderer.h"
|
||||
|
||||
#define TEXTURES_MAX_LAYERS 50
|
||||
#include <vector>
|
||||
|
||||
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<double> 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<Vector3> getLayersDisplacement(const TexturesDefinition *textures, const Vector3 &location,
|
||||
const Vector3 &normal, const vector<double> &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<double> &presence, const vector<Vector3> &location,
|
||||
const vector<Vector3> &normal, double precision, const Vector3 &eye) const;
|
||||
|
||||
private:
|
||||
SoftwareRenderer *parent;
|
||||
bool quality_normal5;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
25
src/tests/TestToolNoise.h
Normal file
25
src/tests/TestToolNoise.h
Normal file
|
@ -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
|
42
src/tests/TexturesRenderer_Test.cpp
Normal file
42
src/tests/TexturesRenderer_Test.cpp
Normal file
|
@ -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<double> 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]);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue