Refactored quality control of terrain rendering

Terrain scaling factor was removed for quality consistency
This commit is contained in:
Michaël Lemaire 2015-09-10 18:16:57 +02:00
parent 4fcf1d071c
commit 6a45c5dba7
12 changed files with 114 additions and 113 deletions

View file

@ -9,7 +9,6 @@ TerrainDefinition::TerrainDefinition(DefinitionNode* parent):
DefinitionNode(parent, "terrain", "terrain")
{
height = 1.0;
scaling = 1.0;
shadow_smoothing = 0.0;
height_map = new TerrainHeightMap(this);
@ -36,8 +35,8 @@ void TerrainDefinition::validate()
/* Get minimal and maximal height */
_height_noise->getRange(&_min_height, &_max_height);
_min_height *= height * scaling;
_max_height *= height * scaling;
_min_height *= height;
_max_height *= height;
/* TODO Alter with heightmap min/max */
}
@ -47,7 +46,6 @@ void TerrainDefinition::copy(DefinitionNode* _destination) const
TerrainDefinition* destination = (TerrainDefinition*)_destination;
destination->height = height;
destination->scaling = scaling;
destination->shadow_smoothing = shadow_smoothing;
height_map->copy(destination->height_map);
@ -62,7 +60,6 @@ void TerrainDefinition::save(PackStream* stream) const
DefinitionNode::save(stream);
stream->write(&height);
stream->write(&scaling);
stream->write(&shadow_smoothing);
_height_noise->save(stream);
}
@ -72,7 +69,6 @@ void TerrainDefinition::load(PackStream* stream)
DefinitionNode::load(stream);
stream->read(&height);
stream->read(&scaling);
stream->read(&shadow_smoothing);
_height_noise->load(stream);
@ -94,8 +90,6 @@ double TerrainDefinition::getGridHeight(int x, int z, bool with_painting)
double TerrainDefinition::getInterpolatedHeight(double x, double z, bool scaled, bool with_painting, bool water_offset)
{
double h;
x /= scaling;
z /= scaling;
if (!with_painting || !height_map->getInterpolatedValue(x, z, &h))
{
@ -104,7 +98,7 @@ double TerrainDefinition::getInterpolatedHeight(double x, double z, bool scaled,
if (scaled)
{
return (water_offset ? (h - water_height->getValue()) : h) * height * scaling;
return (water_offset ? (h - water_height->getValue()) : h) * height;
}
else
{
@ -114,7 +108,7 @@ double TerrainDefinition::getInterpolatedHeight(double x, double z, bool scaled,
double TerrainDefinition::getWaterOffset() const
{
return -water_height->getValue() * height * scaling;
return -water_height->getValue() * height;
}
HeightInfo TerrainDefinition::getHeightInfo()
@ -123,7 +117,6 @@ HeightInfo TerrainDefinition::getHeightInfo()
result.min_height = _min_height;
result.max_height = _max_height;
/* TODO This is duplicated in ter_render.c (_realGetWaterHeight) */
result.base_height = -getWaterOffset();
return result;
@ -146,7 +139,6 @@ void TerrainDefinition::applyPreset(TerrainPreset preset)
_height_noise->addLevelsSimple(resolution - 2, pow(2.0, resolution - 1), -0.7, 0.7, 0.5);
_height_noise->normalizeAmplitude(-1.0, 1.0, 0);
_height_noise->setFunctionParams(NoiseGenerator::NOISE_FUNCTION_SIMPLEX, 0.0, 0.0);
scaling = 1.0;
height = 30.0;
shadow_smoothing = 0.03;
break;

View file

@ -44,7 +44,6 @@ public:
public:
double height;
double scaling;
double shadow_smoothing;
TerrainHeightMap* height_map;

View file

@ -19,7 +19,7 @@ void TerrainHeightMap::copy(DefinitionNode* _destination) const
double TerrainHeightMap::getInitialValue(double x, double y) const
{
return terrain->getInterpolatedHeight(x * terrain->scaling, y * terrain->scaling, false, false);
return terrain->getInterpolatedHeight(x, y, false, false);
}
void TerrainHeightMap::brushElevation(const PaintedGridBrush &brush, double x, double y, double value)

View file

@ -1,4 +1,5 @@
#include "SoftwareCanvasRenderer.h"
#include "TerrainRenderer.h"
#include "Scenery.h"
#include "CameraDefinition.h"
#include "TerrainDefinition.h"
@ -56,10 +57,11 @@ static void testGroundShadowQuality()
SoftwareCanvasRenderer renderer(&scenery);
renderer.setSize(400, 300);
for (int i = 1; i <= 10; i++)
renderer.setQuality(0.2);
for (int i = 0; i < 6; i++)
{
// TODO keep same rasterization across renders, or keep rasterization quality low
renderer.render_quality = i;
renderer.getTerrainRenderer()->setQuality((double)i / 5.0);
startTestRender(&renderer, "ground_shadow_quality", i);
}
}

View file

@ -27,7 +27,7 @@ OpenGLRenderer::OpenGLRenderer(Scenery* scenery):
view_matrix = new QMatrix4x4;
render_quality = 3;
setQuality(0.3);
functions = new OpenGLFunctions();
shared_state = new OpenGLSharedState();

View file

@ -86,6 +86,14 @@ void SoftwareRenderer::prepare()
//fluid_medium->registerMedium(water_renderer);
}
void SoftwareRenderer::setQuality(double quality)
{
terrain_renderer->setQuality(quality);
// TEMP compat with old code
render_quality = (int)(quality * 9.0) + 1;
}
void SoftwareRenderer::disableAtmosphere()
{
LightComponent light;

View file

@ -22,8 +22,6 @@ public:
int render_quality;
CameraDefinition* render_camera;
void* customData[10];
virtual Vector3 getCameraLocation(const Vector3 &target);
virtual Vector3 getCameraDirection(const Vector3 &target);
virtual double getPrecision(const Vector3 &location);
@ -38,6 +36,15 @@ public:
*/
virtual void prepare();
/*!
* Set the global quality control factor.
*
* Values between 0.0 and 1.0 are standard quality (1.0 is considered a "very good" production quality value).
*
* Values above 1.0 are used for boosting ("extra" quality, for demanding renders).
*/
virtual void setQuality(double quality);
/*!
* \brief Disable atmosphere and sky lighting, replacing it by static lights.
*

View file

@ -11,6 +11,27 @@
TerrainRayWalker::TerrainRayWalker(SoftwareRenderer* renderer):
renderer(renderer)
{
setQuality(0.5);
}
void TerrainRayWalker::setQuality(double displacement_safety, double minimal_step, double maximal_step, double step_factor, double max_distance, double escape_step)
{
this->displacement_safety = displacement_safety;
this->minimal_step = minimal_step;
this->maximal_step = maximal_step;
this->step_factor = step_factor;
this->max_distance = max_distance;
this->escape_step = escape_step;
}
void TerrainRayWalker::setQuality(double factor)
{
setQuality(0.2 + 0.8 * factor,
1.0 / (factor * factor * 30.0 + 1.0),
50.0 / (factor * 10.0 + 1.0),
1.0 / (factor * 10.0 + 1.0),
10.0 + factor * 200.0,
factor * factor * 100.0);
}
void TerrainRayWalker::update()
@ -19,16 +40,10 @@ void TerrainRayWalker::update()
HeightInfo info = terrain->getHeightInfo();
TexturesDefinition* textures = renderer->getScenery()->getTextures();
double disp = textures->getMaximalDisplacement();
displacement_base = textures->getMaximalDisplacement();
ymin = info.min_height - disp;
ymax = info.max_height + disp;
ydispmax = disp * (0.5 + (double)renderer->render_quality * 0.05);
ydispmin = -ydispmax;
minstep = 0.5 * terrain->scaling / (double)renderer->render_quality;
maxstep = 50.0 * terrain->scaling / (double)renderer->render_quality;
ymin = info.min_height - displacement_base;
ymax = info.max_height + displacement_base;
}
static inline Vector3 _getShiftAxis(const Vector3 &direction)
@ -44,7 +59,7 @@ static inline Vector3 _getShiftAxis(const Vector3 &direction)
}
}
bool TerrainRayWalker::startWalking(const Vector3 &start, Vector3 direction, double escape_angle, double max_length, TerrainHitResult &result)
bool TerrainRayWalker::startWalking(const Vector3 &start, Vector3 direction, double escape_angle, TerrainHitResult &result)
{
TerrainRenderer* terrain_renderer = renderer->getTerrainRenderer();
TexturesRenderer* textures_renderer = renderer->getTexturesRenderer();
@ -56,21 +71,14 @@ bool TerrainRayWalker::startWalking(const Vector3 &start, Vector3 direction, dou
Vector3 previous_cursor = start;
bool hit = false;
double step_length = minstep;
double step_length = minimal_step;
double walked_length = 0.0;
result.escape_angle = 0.0;
if (escape_angle != 0.0)
{
// Prepare escape
if (renderer->render_quality >= 7)
{
shift_step = escape_angle / (double)(renderer->render_quality * renderer->render_quality);
}
else
{
shift_step = escape_angle / (double)renderer->render_quality;
}
shift_step = escape_angle / escape_step;
shift_matrix = Matrix4::newRotateAxis(-shift_step, _getShiftAxis(direction));
}
@ -84,13 +92,13 @@ bool TerrainRayWalker::startWalking(const Vector3 &start, Vector3 direction, dou
diff = cursor.y - terrain_result.location.y;
// If we are very under the terrain, consider a hit
if (diff < ydispmin)
if (diff < -displacement_base * displacement_safety)
{
hit = true;
}
// If we are close enough to the terrain, apply displacement
else if (diff < ydispmax)
else if (diff < displacement_base * displacement_safety)
{
displaced = textures_renderer->displaceTerrain(terrain_result);
diff = cursor.y - displaced.y;
@ -128,17 +136,17 @@ bool TerrainRayWalker::startWalking(const Vector3 &start, Vector3 direction, dou
previous_cursor = cursor;
walked_length += step_length;
step_length = diff * 10.0 / (double)renderer->render_quality;
if (step_length < minstep)
step_length = diff * step_factor;
if (step_length < minimal_step)
{
step_length = minstep;
step_length = minimal_step;
}
else if (step_length > maxstep)
else if (step_length > maximal_step)
{
step_length = maxstep;
step_length = maximal_step;
}
}
} while (not hit and cursor.y < ymax and walked_length < max_length);
} while (not hit and cursor.y < ymax and walked_length < max_distance);
return hit or result.escape_angle > 0.0;
}

View file

@ -26,6 +26,19 @@ public:
public:
TerrainRayWalker(SoftwareRenderer* renderer);
/**
* Set the walker quality.
*
* @param displacement_safety Safety factor (around 1.0) to detect when displacement textures need to be applied
* @param minimal_step Minimal length of a walking step
* @param maximal_step Maximal length of a walking step
* @param step_factor Precision factor of steps, depending on terrain proximity
* @param max_distance Maximal distance allowed to travel before considering an escape
* @param escape_step Angle step when allowing an escape angle
*/
void setQuality(double displacement_safety, double minimal_step, double maximal_step, double step_factor, double max_distance, double escape_step);
void setQuality(double factor);
/*!
* \brief Update the walker internal data, from the renderer and scenery.
*/
@ -37,20 +50,24 @@ public:
* \param start Point of origin of the ray
* \param direction Ray direction (normalized vector)
* \param escape_angle Maximal angle allowed to escape the terrain on hit (mainly for shadows computing)
* \param max_length Maximum length to walk before considering no hit
* \param result Object to store the results info
* \return true if there was a hit
*/
bool startWalking(const Vector3 &start, Vector3 direction, double escape_angle, double max_length, TerrainHitResult &result);
bool startWalking(const Vector3 &start, Vector3 direction, double escape_angle, TerrainHitResult &result);
private:
SoftwareRenderer* renderer;
double ymin;
double ymax;
double ydispmin;
double ydispmax;
double minstep;
double maxstep;
double displacement_base;
// Quality control
double displacement_safety;
double minimal_step;
double maximal_step;
double step_factor;
double max_distance;
double escape_step;
};
}

View file

@ -10,17 +10,34 @@
TerrainRenderer::TerrainRenderer(SoftwareRenderer* parent):
parent(parent)
{
walker = new TerrainRayWalker(parent);
walker_ray = new TerrainRayWalker(parent);
walker_shadows = new TerrainRayWalker(parent);
quad_normals = false;
}
TerrainRenderer::~TerrainRenderer()
{
delete walker;
delete walker_ray;
delete walker_shadows;
}
void TerrainRenderer::update()
{
walker->update();
walker_ray->update();
walker_shadows->update();
}
void TerrainRenderer::setQuality(bool quad_normals, double ray_precision, double shadow_precision)
{
this->quad_normals = quad_normals;
walker_ray->setQuality(ray_precision);
walker_shadows->setQuality(shadow_precision);
}
void TerrainRenderer::setQuality(double factor)
{
setQuality(factor > 0.6, factor, factor * factor);
}
double TerrainRenderer::getHeight(double x, double z, bool with_painting, bool water_offset)
@ -135,7 +152,7 @@ RayCastingResult TerrainRenderer::castRay(const Vector3 &start, const Vector3 &d
{
RayCastingResult result;
TerrainRayWalker::TerrainHitResult walk_result;
if (walker->startWalking(start, direction.normalize(), 0.0, 200.0, walk_result))
if (walker_ray->startWalking(start, direction.normalize(), 0.0, walk_result))
{
result.hit = true;
result.hit_location = walk_result.hit_location;
@ -176,7 +193,7 @@ bool TerrainRenderer::applyLightFilter(LightComponent &light, const Vector3 &at)
// Walk to find an intersection
double escape_angle = definition->shadow_smoothing;
// TODO max length should depend on the sun light angle and altitude range
if (walker->startWalking(at, direction_to_light, escape_angle, 100.0, walk_result))
if (walker_shadows->startWalking(at, direction_to_light, escape_angle, walk_result))
{
if (walk_result.escape_angle == 0.0)
{

View file

@ -25,6 +25,8 @@ public:
virtual ~TerrainRenderer();
virtual void update();
void setQuality(bool quad_normals, double ray_precision, double shadow_precision);
void setQuality(double factor);
virtual RayCastingResult castRay(const Vector3 &start, const Vector3 &direction);
virtual double getHeight(double x, double z, bool with_painting, bool water_offset=true);
@ -33,8 +35,11 @@ public:
virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override;
private:
SoftwareRenderer* parent;
TerrainRayWalker* walker;
SoftwareRenderer *parent;
TerrainRayWalker *walker_ray;
TerrainRayWalker *walker_shadows;
bool quad_normals;
};
}

View file

@ -30,7 +30,6 @@ protected:
virtual void SetUp() {
terrain = new TerrainDefinition(NULL);
terrain->height = 3.0;
terrain->scaling = 1.0;
terrain->_height_noise->clearLevels();
terrain->propWaterHeight()->setValue(0.0);
NoiseGenerator::NoiseLevel level = {1.0, 2.0, -1.0};
@ -74,21 +73,6 @@ TEST_F(TerrainPainting_Test, grid)
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(0.0, 0.0, 1, 0), 0.0);
EXPECT_DOUBLE_IN_RANGE(terrain->getInterpolatedHeight(0.5, 0.0, 1, 0), 3.0 * 0.1564, 3.0 * 0.1566);
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(1.0, 0.0, 1, 0), 3.0 * sin(1.0 * X_FACTOR));
/* Test scaling */
terrain->scaling = 2.0;
EXPECT_DOUBLE_EQ(terrain->getGridHeight(0, 0, 0), 0.0);
EXPECT_DOUBLE_EQ(terrain->getGridHeight(1, 0, 0), sin(1.0 * X_FACTOR));
EXPECT_DOUBLE_EQ(terrain->getGridHeight(2, 0, 0), sin(2.0 * X_FACTOR));
EXPECT_DOUBLE_EQ(terrain->getGridHeight(3, 0, 0), sin(3.0 * X_FACTOR));
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(0, 0, 0, 0), 0.0);
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(1, 0, 0, 0), sin(0.5 * X_FACTOR));
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(2, 0, 0, 0), sin(1.0 * X_FACTOR));
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(3, 0, 0, 0), sin(1.5 * X_FACTOR));
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(0, 0, 1, 0), 0.0);
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(1, 0, 1, 0), 6.0 * sin(0.5 * X_FACTOR));
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(2, 0, 1, 0), 6.0 * sin(1.0 * X_FACTOR));
EXPECT_DOUBLE_EQ(terrain->getInterpolatedHeight(3, 0, 1, 0), 6.0 * sin(1.5 * X_FACTOR));
}
static void _checkBrushResultSides(TerrainDefinition* terrain, PaintedGridBrush*, double center, double midhard, double hard, double midsoft, double soft, double exter, double neg_midhard, double neg_hard, double neg_midsoft, double neg_soft, double neg_exter)
@ -125,7 +109,6 @@ TEST_F(TerrainPainting_Test, brush_flatten)
/* Set up */
PaintedGridBrush brush(2.0, 2.0, 4.0);
terrain->height = 1.0;
terrain->scaling = 1.0;
terrain->_height_noise->forceValue(0.0);
/* Test flattening center at 0.5 */
@ -149,14 +132,6 @@ TEST_F(TerrainPainting_Test, brush_flatten)
_checkBrushResult(terrain, &brush, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0);
terrain->height_map->brushFlatten(brush, 0.0, 0.0, 0.5, 1.0);
_checkBrushResult(terrain, &brush, 0.05, 0.05, 0.05, 0.025, 0.0, 0.0, 0);
/* Test with scaling modifier */
terrain->height = 10.0;
terrain->scaling = 2.0;
terrain->height_map->clearPainting();
_checkBrushResult(terrain, &brush, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0);
terrain->height_map->brushFlatten(brush, 0.0, 0.0, 0.5, 1.0);
_checkBrushResult(terrain, &brush, 0.05, 0.05, 0.05, 0.025, 0.0, 0.0, 0);
}
TEST_F(TerrainPainting_Test, brush_reset)
@ -165,7 +140,6 @@ TEST_F(TerrainPainting_Test, brush_reset)
PaintedGridBrush brush(2.0, 2.0, 4.0);
PaintedGridBrush brush_full(4.0, 0.0, 4.0);
terrain->height = 1.0;
terrain->scaling = 1.0;
terrain->_height_noise->forceValue(1.0);
/* Test resetting at center */
@ -195,32 +169,4 @@ TEST_F(TerrainPainting_Test, brush_reset)
_checkBrushResult(terrain, &brush, 1.1, 1.1, 1.1, 1.1, 1.1, 1.0, 0);
terrain->height_map->brushReset(brush, 0.0, 0.0, 0.1);
_checkBrushResult(terrain, &brush, 1.099, 1.099, 1.099, 1.0995, 1.1, 1.0, 0);
/* Test with scaling modifier */
terrain->height = 10.0;
terrain->scaling = 2.0;
terrain->height_map->clearPainting();
_checkBrushResult(terrain, &brush, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0);
terrain->height_map->brushFlatten(brush_full, 0.0, 0.0, 2.0, 1.0);
_checkBrushResult(terrain, &brush, 1.1, 1.1, 1.1, 1.1, 1.1, 1.0, 0);
terrain->height_map->brushReset(brush, 0.0, 0.0, 0.1);
_checkBrushResult(terrain, &brush, 1.099, 1.099, 1.099, 1.0995, 1.1, 1.0, 0);
}
TEST_F(TerrainPainting_Test, brush_reset_basevalue)
{
/* Set up */
PaintedGridBrush brush(2.0, 2.0, 4.0);
PaintedGridBrush brush_full(4.0, 0.0, 4.0);
terrain->height = 1.0;
terrain->scaling = 1.0;
/* Test with scaling and the sinusoid setup (to test the basevalue sampling) */
terrain->height = 1.0;
terrain->scaling = 2.0;
_checkBrushResult(terrain, &brush, 0.0, 0.309016994375, 0.587785252292, 0.809016994375, 0.951056516295, 1.0, 1);
terrain->height_map->brushFlatten(brush_full, 0.0, 0.0, 2.0, 1.0);
_checkBrushResultSides(terrain, &brush, 2.0, 2.0, 2.0, 2.0, 2.0, 1.0, 2.0, 2.0, 2.0, 2.0, -1.0);
terrain->height_map->brushReset(brush, 0.0, 0.0, 1.0);
_checkBrushResultSides(terrain, &brush, 0.0, 0.309016994375, 0.587785252292, 2.0 - (2.0 - 0.809016994375) * 0.5, 2.0, 1.0, -0.309016994375, -0.587785252292, 2.0 - (2.0 + 0.809016994375) * 0.5, 2.0, -1.0);
}