WS30: Improved coastlines

Build a high resolution texture containing coastline
data that will be mixed with the landclass texture
in the ws30 shader.

Replaces the previous approach of creating a separate
coastline mesh.
This commit is contained in:
Stuart Buchanan 2022-05-28 21:03:48 +01:00
parent e04f89e8b2
commit a8cb72d653
3 changed files with 139 additions and 143 deletions

View File

@ -440,7 +440,7 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co
// TEXTURE NAME texture-unit Material texture index Default value
// Primary texure 0 0 n/a
// gradient_texture 2 13 Textures/Terrain/rock_alt.png
// dot_texture 3 15 Textures/Terrain/void.png
// dot_texture 3 15 Textures/Terrain/sand6.png
// grain_texture 4 14 Textures/Terrain/grain_texture.png
// mix_texture 5 12 Textures/Terrain/void.png
// detail_texture 7 11 Textures/Terrain/void.png
@ -457,6 +457,7 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co
if (i < 13) texture = std::string("Textures/Terrain/void.png");
if (i == 13) texture = std::string("Textures/Terrain/rock_alt.png");
if (i == 14) texture = std::string("Textures/Terrain/grain_texture.png");
if (i == 15) texture = std::string("Textures/Terrain/sand6.png");
if (i > 14) texture = std::string("Textures/Terrain/void.png");
}

View File

@ -174,7 +174,6 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded)
applyColorLayers(*buffer, masterLocator);
applyLineFeatures(*buffer, masterLocator);
applyAreaFeatures(*buffer, masterLocator);
applyCoastline(*buffer, masterLocator);
applyMaterials(*buffer, masterLocator);
}
}
@ -185,7 +184,6 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded)
applyColorLayers(*buffer, masterLocator);
applyLineFeatures(*buffer, masterLocator);
applyAreaFeatures(*buffer, masterLocator);
applyCoastline(*buffer, masterLocator);
applyMaterials(*buffer, masterLocator);
}
@ -1374,9 +1372,19 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator)
texture2D->setWrap(osg::Texture::WRAP_S,osg::Texture::CLAMP_TO_EDGE);
texture2D->setWrap(osg::Texture::WRAP_T,osg::Texture::CLAMP_TO_EDGE);
osg::ref_ptr<osg::Texture2D> waterTexture = new osg::Texture2D;
waterTexture->setImage(generateWaterTexture(buffer, masterLocator));
waterTexture->setMaxAnisotropy(16.0f);
waterTexture->setResizeNonPowerOfTwoHint(false);
waterTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST_MIPMAP_NEAREST);
waterTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST_MIPMAP_NEAREST);
waterTexture->setWrap(osg::Texture::WRAP_S,osg::Texture::CLAMP_TO_EDGE);
waterTexture->setWrap(osg::Texture::WRAP_T,osg::Texture::CLAMP_TO_EDGE);
osg::StateSet* stateset = buffer._landGeode->getOrCreateStateSet();
stateset->setTextureAttributeAndModes(0, texture2D, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(1, atlas.image, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(7, waterTexture, osg::StateAttribute::ON);
stateset->addUniform(new osg::Uniform(VPBTechnique::PHOTO_SCENERY, false));
stateset->addUniform(new osg::Uniform(VPBTechnique::Z_UP_TRANSFORM, osg::Matrixf(osg::Matrix::inverse(makeZUpFrameRelative(loc)))));
stateset->addUniform(new osg::Uniform(VPBTechnique::MODEL_OFFSET, (osg::Vec3f) buffer._transform->getMatrix().getTrans()));
@ -2087,38 +2095,45 @@ void VPBTechnique::generateAreaFeature(BufferData& buffer, Locator* masterLocato
}
}
void VPBTechnique::applyCoastline(BufferData& buffer, Locator* masterLocator)
{
osg::Image* VPBTechnique::generateWaterTexture(BufferData& buffer, Locator* masterLocator) {
osg::Image* waterTexture = new osg::Image();
unsigned int coast_features_lod_range = 4;
float coastWidth = 40.0;
unsigned int waterTextureSize = 2048;
float coastWidth = 150.0;
const unsigned int tileLevel = _terrainTile->getTileID().level;
const SGPropertyNode* propertyNode = _options->getPropertyNode().get();
if (propertyNode) {
const SGPropertyNode* static_lod = propertyNode->getNode("/sim/rendering/static-lod");
waterTextureSize = static_lod->getIntValue("water-texture-size", coast_features_lod_range);
coast_features_lod_range = static_lod->getIntValue("coastline-lod-level", coast_features_lod_range);
coastWidth = static_lod->getFloatValue("coastline-width", coastWidth);
coastWidth = static_lod->getFloatValue("coastline-width", coastWidth);
}
if (tileLevel < coast_features_lod_range) {
// Do not generate coasts for tiles too far away
return;
waterTexture->allocateImage(1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE);
waterTexture->setColor(osg::Vec4f(0.0f,0.0f,0.0f,0.0f), 0,0);
return waterTexture;
}
const SGMaterialLibPtr matlib = _options->getMaterialLib();
if (! matlib) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to get materials library to generate areas");
return;
}
waterTexture->allocateImage(waterTextureSize, waterTextureSize, 1, GL_RGBA, GL_FLOAT);
for (unsigned int y = 0; y < waterTextureSize; ++y) {
for (unsigned int x = 0; x < waterTextureSize; ++x) {
waterTexture->setColor(osg::Vec4f(0.0f,0.0f,0.0f,0.0f), x, y);
}
}
// Get all appropriate coasts. We assume that the VPB terrain tile is smaller than a Bucket size.
const osg::Vec3d world = buffer._transform->getMatrix().getTrans();
float tileSize = sqrt(buffer._width * buffer._width + buffer._height * buffer._height);
const SGGeod loc = SGGeod::fromCart(toSG(world));
const SGBucket bucket = SGBucket(loc);
auto coasts = std::find_if(_coastFeatureLists.begin(), _coastFeatureLists.end(), [bucket](BucketCoastlineBinList b){return (b.first == bucket);});
if (coasts == _coastFeatureLists.end()) return;
if (coasts == _coastFeatureLists.end()) return waterTexture;
// We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0)
osg::Vec3d up = world;
@ -2126,23 +2141,6 @@ void VPBTechnique::applyCoastline(BufferData& buffer, Locator* masterLocator)
TileBounds tileBounds(masterLocator, up);
SGMaterialCache* matcache = matlib->generateMatCache(loc, _options);
SGMaterial* mat = matcache->find("ws30coastline");
if (!mat) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to find material ws30coastline at " << loc << " " << bucket);
return;
}
unsigned int xsize = mat->get_xsize();
unsigned int ysize = mat->get_ysize();
// Generate a geometry for this set of coasts.
osg::Vec3Array* v = new osg::Vec3Array;
osg::Vec2Array* t = new osg::Vec2Array;
osg::Vec3Array* n = new osg::Vec3Array;
osg::Vec4Array* c = new osg::Vec4Array;
for (; coasts != _coastFeatureLists.end(); ++coasts) {
const CoastlineBinList coastBins = coasts->second;
@ -2155,128 +2153,131 @@ void VPBTechnique::applyCoastline(BufferData& buffer, Locator* masterLocator)
if (clipped.size() > 1) {
// We need at least two points to render a line.
LineFeatureBin::LineFeature line = LineFeatureBin::LineFeature(clipped, coastWidth);
generateCoastlineFeature(buffer, masterLocator, line, world, v, t, n, xsize, ysize);
addCoastline(masterLocator, waterTexture, line, waterTextureSize, tileSize, coastWidth);
}
}
}
}
if (v->size() == 0) return;
c->push_back(osg::Vec4(1.0,1.0,1.0,1.0));
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
geometry->setVertexArray(v);
geometry->setTexCoordArray(0, t, osg::Array::BIND_PER_VERTEX);
geometry->setTexCoordArray(1, t, osg::Array::BIND_PER_VERTEX);
geometry->setNormalArray(n, osg::Array::BIND_PER_VERTEX);
geometry->setColorArray(c, osg::Array::BIND_OVERALL);
geometry->setUseDisplayList( false );
geometry->setUseVertexBufferObjects( true );
geometry->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLES, 0, v->size()) );
EffectGeode* geode = new EffectGeode;
geode->addDrawable(geometry);
geode->setMaterial(mat);
geode->setEffect(mat->get_one_effect(0));
geode->setNodeMask(SG_NODEMASK_TERRAIN_BIT);
buffer._transform->addChild(geode);
return waterTexture;
}
void VPBTechnique::generateCoastlineFeature(BufferData& buffer, Locator* masterLocator, LineFeatureBin::LineFeature coastline, osg::Vec3d modelCenter, osg::Vec3Array* v, osg::Vec2Array* t, osg::Vec3Array* n, unsigned int xsize, unsigned int ysize)
{
if (coastline._nodes.size() < 2) {
// Update the color of a particular texture pixel, clipping to the texture and not over-writing a larger value.
void VPBTechnique::updateWaterTexture(osg::Image* waterTexture, unsigned int waterTextureSize, osg::Vec4 color, float x, float y) {
if (floor(x) < 0.0) return;
if (floor(x) > (waterTextureSize -1)) return;
if (floor(y) < 0.0) return;
if (floor(y) > (waterTextureSize -1)) return;
auto clamp = [waterTextureSize](float l) { return (unsigned int) fmaxf(0, fminf(floor(l), waterTextureSize - 1)); };
unsigned int clamped_x = clamp(x);
unsigned int clamped_y = clamp(y);
osg::Vec4 new_color = max(waterTexture->getColor(clamped_x, clamped_y), color);
waterTexture->setColor(new_color, clamped_x, clamped_y);
}
void VPBTechnique::writeShoreStripe(osg::Image* waterTexture, unsigned int waterTextureSize, float tileSize, float coastWidth, float x, float y, int dx, int dy) {
// We need to create a shoreline of width coastWidth to define it better and to hide any discrepancy in the underlying terrain mesh
// which may be either mis-aligned or simply of insufficient resolution.
float waterTextureResolution = tileSize / waterTextureSize;
int width = (int) coastWidth / waterTextureResolution;
int aboveHWLtxUnits = 0.3*width;
int belowHWLtxUnits = width - aboveHWLtxUnits;
// Above the high water mark we just want sand. However there can be interpolation artifacts right at the edge causing a border or water. To avoid
// this we create an extra texel where the G channel is used to ensure we get a hard fall-off to the underlying texture
// on the adjacent sand texture unit.
updateWaterTexture(waterTexture, waterTextureSize, osg::Vec4(0.0f, 1.0f, 0.0f, 0.0), x - dx*(aboveHWLtxUnits + 1), y -dy*(aboveHWLtxUnits + 1));
// Create the sand above the high water mark
for (int d = 0; d < aboveHWLtxUnits; d++) {
updateWaterTexture(waterTexture, waterTextureSize, osg::Vec4(0.0f, 1.0f, 0.0f, 0.0), x - dx*d, y -dy*d);
}
// Create the shoreline below the high water level which will gradually merge into the underlying terrain mesh
for (int d = 0; d < belowHWLtxUnits; d++) {
updateWaterTexture(waterTexture, waterTextureSize, osg::Vec4(0.0f, 0.0f, 1.0f, 0.0)*(belowHWLtxUnits -d)/belowHWLtxUnits, x + dx*d, y + dy*d);
}
}
void VPBTechnique::addCoastline(Locator* masterLocator, osg::Image* waterTexture, LineFeatureBin::LineFeature line, unsigned int waterTextureSize, float tileSize, float coastWidth) {
if (line._nodes.size() < 2) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Coding error - LineFeatureBin::LineFeature with fewer than two nodes");
return;
}
osg::Vec3d ma, mb;
std::list<osg::Vec3d> coastlinePoints;
auto coastline_iter = coastline._nodes.begin();
auto iter = line._nodes.begin();
// We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0)
osg::Vec3d up = modelCenter;
up.normalize();
ma = getMeshIntersection(buffer, masterLocator, *coastline_iter - modelCenter, up);
coastline_iter++;
for (; coastline_iter != coastline._nodes.end(); coastline_iter++) {
mb = getMeshIntersection(buffer, masterLocator, *coastline_iter - modelCenter, up);
auto esl = VPBElevationSlice::computeVPBElevationSlice(buffer._landGeometry, ma, mb, up);
for(auto eslitr = esl.begin(); eslitr != esl.end(); ++eslitr) {
coastlinePoints.push_back(*eslitr);
}
// Now traverse the next segment
ma = mb;
// LineFeature is in Model coordinates, but for the rasterization we will use local coordinates.
osg::Vec3d start;
bool success = masterLocator->convertModelToLocal(*iter, start);
if (!success) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to convert from model coordinates to local: " << *iter);
return;
}
if (coastlinePoints.size() == 0) return;
// We now have a series of points following the topography of the elevation mesh.
auto iter = coastlinePoints.begin();
osg::Vec3d start = *iter;
iter++;
for (; iter != line._nodes.end(); iter++) {
osg::Vec3d end;
success = masterLocator->convertModelToLocal(*iter, end);
if (!success) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to convert from model coordinates to local: " << *iter);
continue;
}
osg::Vec3d last_spanwise = (*iter - start)^ up;
last_spanwise.normalize();
// We now have two points in local (2d) space, so rasterize the line between them onto the water texture.
// We use a simple version of a DDA to render the lines. We should replace this with a scanline algorithm to handle coastlines
// and waterbodies efficiently.
// Do everything in the texture coordinates.
float dx = (end.x() - start.x()) * waterTextureSize;
float dy = (end.y() - start.y()) * waterTextureSize;
float step = abs(dy);
bool steep = true;
if (abs(dx) >= abs(dy)) {
step = abs(dx);
steep = false;
}
dx = dx / step;
dy = dy / step;
float x = start.x() * waterTextureSize;
float y = start.y() * waterTextureSize;
int i = 0;
while (i <= step) {
if ((x >= 0.0f) && (y >= 0.0f) && (x < waterTextureSize) && (y < waterTextureSize)) {
// The line defines the Mean High Water Level. By definition, the sea is always to
// the right of the line. We want to fill in a section of sea and shore to cover up any issues
// between the OSM data and the landclass texture.
float yTexBaseA = 0.0f;
float yTexBaseB = 0.0f;
if (steep) {
// A steep line, so we are should add width in the x-axis
if (dy < 0) {
// We are travelling downwards, so the seaward side is -x
writeShoreStripe(waterTexture, waterTextureSize, tileSize, coastWidth, x , y, -1, 0);
} else {
// We are travelling upwards, so the seaward side is +x
writeShoreStripe(waterTexture, waterTextureSize, tileSize, coastWidth, x , y, 1, 0);
}
} else {
// Not a steep line, so we should add width in the y axis
if (dx < 0) {
// We are travelling right to left, so the seaward side is +y
writeShoreStripe(waterTexture, waterTextureSize, tileSize, coastWidth, x , y, 0, 1);
} else {
// We are travelling left to right, so the seaward side is -y
writeShoreStripe(waterTexture, waterTextureSize, tileSize, coastWidth, x , y, 0, -1);
}
}
}
for (; iter != coastlinePoints.end(); iter++) {
osg::Vec3d end = *iter;
// Ignore small segments - we really don't need resolution less than 10m
if ((end - start).length2() < 100.0) continue;
// Find a spanwise vector
osg::Vec3d spanwise = ((end-start) ^ up);
spanwise.normalize();
// Define the coastline extents. Angle it down slightly on the seaward side (b->d).
// OSM coastlines are always with the
const osg::Vec3d a = start + up;
const osg::Vec3d b = start + last_spanwise * coastline._width;
const osg::Vec3d c = end + up;
const osg::Vec3d d = end + spanwise * coastline._width;
// Determine the x and y texture coordinates for the edges
const float xTex = coastline._width / xsize;
const float yTexA = yTexBaseA + (c-a).length() / ysize;
const float yTexB = yTexBaseB + (d-b).length() / ysize;
// Now generate two triangles, .
v->push_back(a);
v->push_back(b);
v->push_back(c);
t->push_back(osg::Vec2d(0, yTexBaseA));
t->push_back(osg::Vec2d(xTex, yTexBaseB));
t->push_back(osg::Vec2d(0, yTexA));
v->push_back(b);
v->push_back(d);
v->push_back(c);
t->push_back(osg::Vec2d(xTex, yTexBaseB));
t->push_back(osg::Vec2d(xTex, yTexB));
t->push_back(osg::Vec2d(0, yTexA));
// Normal is straight from the quad
osg::Vec3d normal = -(end-start)^spanwise;
normal.normalize();
for (unsigned int i = 0; i < 6; i++) n->push_back(normal);
x = x + dx;
y = y + dy;
i = i + 1;
}
start = end;
yTexBaseA = yTexA;
yTexBaseB = yTexB;
last_spanwise = spanwise;
}
}

View File

@ -169,16 +169,10 @@ class VPBTechnique : public TerrainTechnique
unsigned int xsize,
unsigned int ysize);
virtual void applyCoastline(BufferData& buffer, Locator* masterLocator);
virtual void generateCoastlineFeature(BufferData& buffer,
Locator* masterLocator,
LineFeatureBin::LineFeature coastLine,
osg::Vec3d modelCenter,
osg::Vec3Array* v,
osg::Vec2Array* t,
osg::Vec3Array* n,
unsigned int xsize,
unsigned int ysize);
virtual osg::Image* generateWaterTexture(BufferData& buffer, Locator* masterLocator);
virtual void addCoastline(Locator* masterLocator, osg::Image* waterTexture, LineFeatureBin::LineFeature line, unsigned int waterTextureSize, float tileSize, float coastWidth);
virtual void updateWaterTexture(osg::Image* waterTexture, unsigned int waterTextureSize, osg::Vec4 color, float x, float y);
virtual void writeShoreStripe(osg::Image* waterTexture, unsigned int waterTextureSize, float tileSize, float coastWidth, float x, float y, int dx, int dy);
virtual osg::Vec3d getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt, osg::Vec3d up);