3440 lines
105 KiB
C++
3440 lines
105 KiB
C++
/*
|
|
* Copyright (C) 2012 Open Source Robotics Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
// Ensure that Winsock2.h is included before Windows.h, which can get
|
|
// pulled in by anybody (e.g., Boost).
|
|
#include <Winsock2.h>
|
|
#endif
|
|
|
|
#include <memory>
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#include "gazebo/common/Assert.hh"
|
|
#include "gazebo/common/CommonIface.hh"
|
|
#include "gazebo/common/Dem.hh"
|
|
#include "gazebo/common/Exception.hh"
|
|
#include "gazebo/common/HeightmapData.hh"
|
|
#include "gazebo/common/SystemPaths.hh"
|
|
#include "gazebo/math/Helpers.hh"
|
|
#include "gazebo/transport/TransportIface.hh"
|
|
#include "gazebo/rendering/ogre_gazebo.h"
|
|
#include "gazebo/rendering/RTShaderSystem.hh"
|
|
#include "gazebo/rendering/Scene.hh"
|
|
#include "gazebo/rendering/Light.hh"
|
|
#include "gazebo/rendering/Conversions.hh"
|
|
#include "gazebo/rendering/UserCamera.hh"
|
|
#include "gazebo/rendering/RenderEngine.hh"
|
|
|
|
#include "gazebo/rendering/Heightmap.hh"
|
|
#include "gazebo/rendering/HeightmapPrivate.hh"
|
|
|
|
using namespace gazebo;
|
|
using namespace rendering;
|
|
|
|
const double HeightmapPrivate::loadRadiusFactor = 1.0;
|
|
const double HeightmapPrivate::holdRadiusFactor = 1.15;
|
|
const boost::filesystem::path HeightmapPrivate::pagingDirname = "paging";
|
|
const boost::filesystem::path HeightmapPrivate::hashFilename = "gzterrain.SHA1";
|
|
|
|
static std::string glslVersion = "130";
|
|
static std::string vpInStr = "in";
|
|
static std::string vpOutStr = "out";
|
|
static std::string fpInStr = "in";
|
|
static std::string fpOutStr = "out";
|
|
static std::string textureStr = "texture";
|
|
|
|
//////////////////////////////////////////////////
|
|
Heightmap::Heightmap(ScenePtr _scene)
|
|
: dataPtr(new HeightmapPrivate)
|
|
{
|
|
this->dataPtr->scene = _scene;
|
|
|
|
this->dataPtr->terrainIdx = 0;
|
|
this->dataPtr->useTerrainPaging = false;
|
|
this->dataPtr->terrainHashChanged = true;
|
|
this->dataPtr->terrainsImported = true;
|
|
|
|
this->dataPtr->gzPagingDir =
|
|
common::SystemPaths::Instance()->GetLogPath() /
|
|
this->dataPtr->pagingDirname;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
Heightmap::~Heightmap()
|
|
{
|
|
this->dataPtr->scene.reset();
|
|
|
|
if (this->dataPtr->terrainPaging)
|
|
{
|
|
OGRE_DELETE this->dataPtr->terrainPaging;
|
|
this->dataPtr->pageManager->destroyWorld(this->dataPtr->world);
|
|
OGRE_DELETE this->dataPtr->pageManager;
|
|
}
|
|
else
|
|
{
|
|
this->dataPtr->terrainGroup->removeAllTerrains();
|
|
|
|
OGRE_DELETE this->dataPtr->terrainGroup;
|
|
this->dataPtr->terrainGroup = nullptr;
|
|
}
|
|
|
|
OGRE_DELETE this->dataPtr->terrainGlobals;
|
|
this->dataPtr->terrainGlobals = nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void Heightmap::LoadFromMsg(ConstVisualPtr &_msg)
|
|
{
|
|
this->dataPtr->terrainSize =
|
|
msgs::ConvertIgn(_msg->geometry().heightmap().size());
|
|
this->dataPtr->terrainOrigin =
|
|
msgs::ConvertIgn(_msg->geometry().heightmap().origin());
|
|
|
|
for (int i = 0; i < _msg->geometry().heightmap().texture_size(); ++i)
|
|
{
|
|
std::string diffusePath = common::find_file(
|
|
_msg->geometry().heightmap().texture(i).diffuse());
|
|
std::string normalPath = common::find_file(
|
|
_msg->geometry().heightmap().texture(i).normal());
|
|
|
|
RenderEngine::Instance()->AddResourcePath(diffusePath);
|
|
RenderEngine::Instance()->AddResourcePath(normalPath);
|
|
|
|
this->dataPtr->diffuseTextures.push_back(diffusePath);
|
|
this->dataPtr->normalTextures.push_back(normalPath);
|
|
this->dataPtr->worldSizes.push_back(
|
|
_msg->geometry().heightmap().texture(i).size());
|
|
}
|
|
|
|
for (int i = 0; i < _msg->geometry().heightmap().blend_size(); ++i)
|
|
{
|
|
this->dataPtr->blendHeight.push_back(
|
|
_msg->geometry().heightmap().blend(i).min_height());
|
|
this->dataPtr->blendFade.push_back(
|
|
_msg->geometry().heightmap().blend(i).fade_dist());
|
|
}
|
|
|
|
if (_msg->geometry().heightmap().has_use_terrain_paging())
|
|
{
|
|
this->dataPtr->useTerrainPaging =
|
|
_msg->geometry().heightmap().use_terrain_paging();
|
|
}
|
|
|
|
if (_msg->geometry().heightmap().has_filename())
|
|
{
|
|
std::string uri = _msg->geometry().heightmap().filename();
|
|
if (!uri.empty())
|
|
{
|
|
this->dataPtr->filename = common::find_file(
|
|
_msg->geometry().heightmap().filename());
|
|
if (this->dataPtr->filename.empty())
|
|
{
|
|
gzerr << "Unable to find file "
|
|
<< _msg->geometry().heightmap().filename()
|
|
<< std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_msg->geometry().heightmap().has_sampling())
|
|
{
|
|
unsigned int s = _msg->geometry().heightmap().sampling();
|
|
if (!ignition::math::isPowerOfTwo(s))
|
|
{
|
|
gzerr << "Heightmap sampling value must be a power of 2. "
|
|
<< "The default value of 2 will be used instead." << std::endl;
|
|
this->dataPtr->sampling = 2u;
|
|
}
|
|
else
|
|
{
|
|
this->dataPtr->sampling = s;
|
|
}
|
|
}
|
|
|
|
this->Load();
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
Ogre::TerrainGroup *Heightmap::GetOgreTerrain() const
|
|
{
|
|
return this->OgreTerrain();
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
Ogre::TerrainGroup *Heightmap::OgreTerrain() const
|
|
{
|
|
return this->dataPtr->terrainGroup;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
common::Image Heightmap::GetImage() const
|
|
{
|
|
return this->Image();
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
common::Image Heightmap::Image() const
|
|
{
|
|
common::Image result;
|
|
|
|
double height = 0.0;
|
|
unsigned char *imageData = nullptr;
|
|
|
|
/// \todo Support multiple terrain objects
|
|
Ogre::Terrain *terrain = this->dataPtr->terrainGroup->getTerrain(0, 0);
|
|
|
|
GZ_ASSERT(terrain != nullptr, "Unable to get a valid terrain pointer");
|
|
|
|
double minHeight = terrain->getMinHeight();
|
|
double maxHeight = terrain->getMaxHeight() - minHeight;
|
|
|
|
// Get the number of vertices along one side of the terrain
|
|
uint16_t size = terrain->getSize();
|
|
|
|
// Create the image data buffer
|
|
imageData = new unsigned char[size * size];
|
|
|
|
// Get height data from all vertices
|
|
for (uint16_t y = 0; y < size; ++y)
|
|
{
|
|
for (uint16_t x = 0; x < size; ++x)
|
|
{
|
|
// Normalize height value
|
|
// Weird Ogre issue: terrain->getHeightAtPoint could return a value
|
|
// larger than terrain->getMaxHeight().
|
|
height = (std::min(terrain->getHeightAtPoint(x, y),
|
|
terrain->getMaxHeight()) - minHeight) / maxHeight;
|
|
|
|
GZ_ASSERT((height <= 1.0 + 1e-6),
|
|
"Normalized terrain height > 1.0");
|
|
GZ_ASSERT((height >= 0.0 - 1e-6),
|
|
"Normalized terrain height < 0.0");
|
|
|
|
// Scale height to a value between 0 and 255
|
|
imageData[(size - y - 1)*size+x] =
|
|
static_cast<unsigned char>(height * 255.0);
|
|
}
|
|
}
|
|
|
|
result.SetFromData(imageData, size, size, common::Image::L_INT8);
|
|
|
|
delete [] imageData;
|
|
return result;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void Heightmap::SplitHeights(const std::vector<float> &_heightmap,
|
|
const int _n, std::vector<std::vector<float> > &_v)
|
|
{
|
|
// We support splitting the terrain in 4 or 16 pieces
|
|
GZ_ASSERT(_n == 4 || _n == 16,
|
|
"Invalid number of terrain divisions (it should be 4 or 16)");
|
|
|
|
int count = 0;
|
|
int width = sqrt(_heightmap.size());
|
|
int newWidth = 1 + (width - 1) / sqrt(_n);
|
|
|
|
// Memory allocation
|
|
_v.resize(_n);
|
|
|
|
for (int tileR = 0; tileR < sqrt(_n); ++tileR)
|
|
{
|
|
int tileIndex = tileR * sqrt(_n);
|
|
for (int row = 0; row < newWidth - 1; ++row)
|
|
{
|
|
for (int tileC = 0; tileC < sqrt(_n); ++tileC)
|
|
{
|
|
for (int col = 0; col < newWidth - 1; ++col)
|
|
{
|
|
_v[tileIndex].push_back(_heightmap[count]);
|
|
++count;
|
|
}
|
|
// Copy last value into the last column
|
|
_v[tileIndex].push_back(_v[tileIndex].back());
|
|
|
|
tileIndex = tileR * sqrt(_n) +
|
|
(tileIndex + 1) % static_cast<int>(sqrt(_n));
|
|
}
|
|
++count;
|
|
}
|
|
// Copy the last row
|
|
for (int i = 0; i < sqrt(_n); ++i)
|
|
{
|
|
tileIndex = tileR * sqrt(_n) + i;
|
|
std::vector<float> lastRow(_v[tileIndex].end() - newWidth,
|
|
_v[tileIndex].end());
|
|
_v[tileIndex].insert(_v[tileIndex].end(),
|
|
lastRow.begin(), lastRow.end());
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void Heightmap::UpdateTerrainHash(const std::string &_hash,
|
|
const boost::filesystem::path &_terrainDir)
|
|
{
|
|
std::ofstream terrainHashFile;
|
|
boost::filesystem::path terrainHashFullPath;
|
|
|
|
// Create the subdirectories if they do not exist
|
|
boost::filesystem::create_directories(_terrainDir);
|
|
|
|
terrainHashFullPath = _terrainDir / this->dataPtr->hashFilename;
|
|
|
|
// Update the terrain hash
|
|
terrainHashFile.open(terrainHashFullPath.string().c_str());
|
|
|
|
// Throw an error if we couldn't open the file for writing.
|
|
if (terrainHashFile.is_open())
|
|
{
|
|
terrainHashFile << _hash;
|
|
terrainHashFile.close();
|
|
}
|
|
else
|
|
{
|
|
gzerr << "Unable to open file for creating a terrain hash: [" +
|
|
terrainHashFullPath.string() + "]\n";
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
bool Heightmap::PrepareTerrain(
|
|
const boost::filesystem::path &_terrainDirPath)
|
|
{
|
|
std::string heightmapHash;
|
|
boost::filesystem::path terrainHashFullPath;
|
|
bool updateHash = true;
|
|
|
|
// Compute the original heightmap's image.
|
|
heightmapHash = common::get_sha1<std::vector<float> >(this->dataPtr->heights);
|
|
|
|
// Check if the terrain hash exists
|
|
terrainHashFullPath = _terrainDirPath / this->dataPtr->hashFilename;
|
|
if (boost::filesystem::exists(terrainHashFullPath))
|
|
{
|
|
try
|
|
{
|
|
// Read the terrain hash
|
|
std::ifstream in(terrainHashFullPath.string().c_str());
|
|
std::stringstream buffer;
|
|
buffer << in.rdbuf();
|
|
std::string terrainHash(buffer.str());
|
|
updateHash = terrainHash != heightmapHash;
|
|
}
|
|
catch(std::ifstream::failure &e)
|
|
{
|
|
gzerr << "Terrain paging error: Unable to read terrain hash\n";
|
|
}
|
|
}
|
|
|
|
// Update the terrain hash and split the terrain into small pieces
|
|
if (updateHash)
|
|
{
|
|
this->UpdateTerrainHash(heightmapHash, _terrainDirPath);
|
|
}
|
|
|
|
return updateHash;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void Heightmap::Load()
|
|
{
|
|
if (this->dataPtr->terrainGlobals != nullptr)
|
|
return;
|
|
|
|
const Ogre::RenderSystemCapabilities *capabilities;
|
|
Ogre::RenderSystemCapabilities::ShaderProfiles profiles;
|
|
Ogre::RenderSystemCapabilities::ShaderProfiles::const_iterator iter;
|
|
|
|
capabilities =
|
|
Ogre::Root::getSingleton().getRenderSystem()->getCapabilities();
|
|
Ogre::DriverVersion glVersion;
|
|
glVersion.build = 0;
|
|
glVersion.major = 3;
|
|
glVersion.minor = 0;
|
|
glVersion.release = 0;
|
|
if (capabilities->isDriverOlderThanVersion(glVersion))
|
|
{
|
|
glslVersion = "120";
|
|
vpInStr = "attribute";
|
|
vpOutStr = "varying";
|
|
fpInStr = "varying";
|
|
textureStr = "texture2D";
|
|
}
|
|
|
|
// The terraingGroup is composed by a number of terrains (1 by default)
|
|
int nTerrains = 1;
|
|
|
|
this->dataPtr->terrainGlobals = new Ogre::TerrainGlobalOptions();
|
|
|
|
#if (OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR >= 8) || \
|
|
OGRE_VERSION_MAJOR > 1
|
|
// Vertex compression breaks anything, e.g. Gpu laser, that tries to build
|
|
// a depth map.
|
|
this->dataPtr->terrainGlobals->setUseVertexCompressionWhenAvailable(false);
|
|
#endif
|
|
|
|
// There is an issue with OGRE terrain LOD if heights are not relative to 0.
|
|
// So we move the heightmap so that its min elevation = 0 before feeding to
|
|
// ogre. It is later translated back by the setOrigin call.
|
|
double minElevation = 0.0;
|
|
|
|
// try loading heightmap data locally
|
|
if (!this->dataPtr->filename.empty())
|
|
{
|
|
this->dataPtr->heightmapData = common::HeightmapDataLoader::LoadTerrainFile(
|
|
this->dataPtr->filename);
|
|
|
|
if (this->dataPtr->heightmapData)
|
|
{
|
|
// TODO add a virtual HeightmapData::GetMinElevation function to avoid the
|
|
// ifdef check. i.e. heightmapSizeZ = GetMaxElevation - GetMinElevation
|
|
double heightmapSizeZ = this->dataPtr->heightmapData->GetMaxElevation();
|
|
#ifdef HAVE_GDAL
|
|
auto demData =
|
|
dynamic_cast<common::Dem *>(this->dataPtr->heightmapData);
|
|
if (demData)
|
|
{
|
|
heightmapSizeZ = heightmapSizeZ - demData->GetMinElevation();
|
|
if (this->dataPtr->terrainSize == ignition::math::Vector3d::Zero)
|
|
{
|
|
this->dataPtr->terrainSize = ignition::math::Vector3d(
|
|
demData->GetWorldWidth(), demData->GetWorldHeight(),
|
|
heightmapSizeZ);
|
|
}
|
|
minElevation = demData->GetMinElevation();
|
|
}
|
|
#endif
|
|
|
|
// these params need to be the same as physics/HeightmapShape.cc
|
|
// in order to generate consistent height data
|
|
bool flipY = false;
|
|
// sampling size along image width and height
|
|
unsigned int vertSize = (this->dataPtr->heightmapData->GetWidth() *
|
|
this->dataPtr->sampling) - this->dataPtr->sampling + 1;
|
|
ignition::math::Vector3d scale;
|
|
scale.X(this->dataPtr->terrainSize.X() / vertSize);
|
|
scale.Y(this->dataPtr->terrainSize.Y() / vertSize);
|
|
|
|
if (ignition::math::equal(heightmapSizeZ, 0.0))
|
|
scale.Z(1.0);
|
|
else
|
|
scale.Z(fabs(this->dataPtr->terrainSize.Z()) / heightmapSizeZ);
|
|
|
|
// Construct the heightmap lookup table
|
|
std::vector<float> lookup;
|
|
this->dataPtr->heightmapData->FillHeightMap(this->dataPtr->sampling,
|
|
vertSize, this->dataPtr->terrainSize, scale, flipY, lookup);
|
|
|
|
for (unsigned int y = 0; y < vertSize; ++y)
|
|
{
|
|
for (unsigned int x = 0; x < vertSize; ++x)
|
|
{
|
|
int index = (vertSize - y - 1) * vertSize + x;
|
|
this->dataPtr->heights.push_back(lookup[index] - minElevation);
|
|
}
|
|
}
|
|
|
|
this->dataPtr->dataSize = vertSize;
|
|
}
|
|
}
|
|
|
|
// if heightmap fails to load locally, get the data from the server side
|
|
if (this->dataPtr->heights.empty())
|
|
{
|
|
gzmsg << "Heightmap could not be loaded locally "
|
|
<< "(is it in the GAZEBO_RESOURCE_PATH?)- requesting data from "
|
|
<< "the server" << std::endl;
|
|
|
|
msgs::Geometry geomMsg;
|
|
|
|
boost::shared_ptr<msgs::Response> response = transport::request(
|
|
this->dataPtr->scene->Name(), "heightmap_data");
|
|
|
|
if (response->response() != "error" &&
|
|
response->type() == geomMsg.GetTypeName())
|
|
{
|
|
geomMsg.ParseFromString(response->serialized_data());
|
|
|
|
// Copy the height data.
|
|
this->dataPtr->terrainSize = msgs::ConvertIgn(geomMsg.heightmap().size());
|
|
this->dataPtr->heights.resize(geomMsg.heightmap().heights().size());
|
|
memcpy(&this->dataPtr->heights[0], geomMsg.heightmap().heights().data(),
|
|
sizeof(this->dataPtr->heights[0]) *
|
|
geomMsg.heightmap().heights().size());
|
|
|
|
this->dataPtr->dataSize = geomMsg.heightmap().width();
|
|
}
|
|
}
|
|
|
|
if (this->dataPtr->heights.empty())
|
|
{
|
|
gzerr << "Failed to load terrain. Heightmap data is empty" << std::endl;
|
|
return;
|
|
}
|
|
|
|
if (!ignition::math::isPowerOfTwo(this->dataPtr->dataSize - 1))
|
|
{
|
|
gzerr << "Heightmap image size must be square, with a size of 2^n+1"
|
|
<< std::endl;
|
|
return;
|
|
}
|
|
|
|
boost::filesystem::path imgPath;
|
|
boost::filesystem::path terrainName;
|
|
boost::filesystem::path terrainDirPath;
|
|
boost::filesystem::path prefix;
|
|
if (!this->dataPtr->filename.empty())
|
|
{
|
|
// Get the full path of the image heightmap
|
|
imgPath = this->dataPtr->filename;
|
|
terrainName = imgPath.filename().stem();
|
|
terrainDirPath = this->dataPtr->gzPagingDir / terrainName;
|
|
|
|
// Add the top level terrain paging directory to the OGRE
|
|
// ResourceGroupManager
|
|
if (!Ogre::ResourceGroupManager::getSingleton().resourceLocationExists(
|
|
this->dataPtr->gzPagingDir.string(), "General"))
|
|
{
|
|
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
|
|
this->dataPtr->gzPagingDir.string(), "FileSystem", "General", true);
|
|
Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup(
|
|
"General");
|
|
}
|
|
}
|
|
|
|
// If the paging is enabled we modify the number of subterrains
|
|
if (this->dataPtr->useTerrainPaging)
|
|
{
|
|
this->dataPtr->splitTerrain = true;
|
|
nTerrains = this->dataPtr->numTerrainSubdivisions;
|
|
prefix = terrainDirPath / "gazebo_terrain_cache";
|
|
}
|
|
else
|
|
{
|
|
// Note: ran into problems with LOD height glitches if heightmap size is
|
|
// larger than 4096 so split it into chunks
|
|
// Note: dataSize should be 2^n + 1
|
|
if (this->dataPtr->maxPixelError > 0 && this->dataPtr->dataSize > 4096u)
|
|
{
|
|
this->dataPtr->splitTerrain = true;
|
|
if (this->dataPtr->dataSize == 4097u)
|
|
this->dataPtr->numTerrainSubdivisions = 4u;
|
|
else
|
|
this->dataPtr->numTerrainSubdivisions = 16u;
|
|
nTerrains = this->dataPtr->numTerrainSubdivisions;
|
|
|
|
gzmsg << "Large heightmap used with LOD. It will be subdivided into " <<
|
|
this->dataPtr->numTerrainSubdivisions << " terrains." << std::endl;
|
|
}
|
|
prefix = terrainDirPath / "gazebo_terrain";
|
|
}
|
|
|
|
double sqrtN = sqrt(nTerrains);
|
|
|
|
// Create terrain group, which holds all the individual terrain instances.
|
|
// Param 1: Pointer to the scene manager
|
|
// Param 2: Alignment plane
|
|
// Param 3: Number of vertices along one edge of the terrain (2^n+1).
|
|
// Terrains must be square, with each side a power of 2 in size
|
|
// Param 4: World size of each terrain instance, in meters.
|
|
|
|
this->dataPtr->terrainGroup = new Ogre::TerrainGroup(
|
|
this->dataPtr->scene->OgreSceneManager(), Ogre::Terrain::ALIGN_X_Y,
|
|
1 + ((this->dataPtr->dataSize - 1) / sqrtN),
|
|
this->dataPtr->terrainSize.X() / (sqrtN));
|
|
|
|
this->dataPtr->terrainGroup->setFilenameConvention(
|
|
Ogre::String(prefix.string()), Ogre::String("dat"));
|
|
|
|
Ogre::Vector3 orig = Conversions::Convert(this->dataPtr->terrainOrigin);
|
|
ignition::math::Vector3d origin(orig.x -0.5 * this->dataPtr->terrainSize.X() +
|
|
0.5 * this->dataPtr->terrainSize.X() / sqrtN,
|
|
orig.y -0.5 * this->dataPtr->terrainSize.X() +
|
|
0.5 * this->dataPtr->terrainSize.X() / sqrtN,
|
|
orig.z + minElevation);
|
|
|
|
this->dataPtr->terrainGroup->setOrigin(Conversions::Convert(origin));
|
|
this->ConfigureTerrainDefaults();
|
|
|
|
if (!this->dataPtr->heights.empty())
|
|
{
|
|
UserCameraPtr userCam = this->dataPtr->scene->GetUserCamera(0);
|
|
|
|
// Move the camera above the terrain only if the user did not modify the
|
|
// camera position in the world file
|
|
if (userCam && !userCam->IsCameraSetInWorldFile())
|
|
{
|
|
double h = *std::max_element(
|
|
&this->dataPtr->heights[0],
|
|
&this->dataPtr->heights[0] + this->dataPtr->heights.size());
|
|
|
|
ignition::math::Vector3d camPos(5, -5, h + 200);
|
|
ignition::math::Vector3d lookAt(0, 0, h);
|
|
ignition::math::Vector3d delta = lookAt - camPos;
|
|
|
|
double yaw = atan2(delta.Y(), delta.X());
|
|
double pitch = atan2(-delta.Z(),
|
|
sqrt(delta.X() * delta.X() + delta.Y() * delta.Y()));
|
|
|
|
userCam->SetWorldPose(ignition::math::Pose3d(camPos,
|
|
ignition::math::Quaterniond(0, pitch, yaw)));
|
|
}
|
|
}
|
|
|
|
this->dataPtr->terrainHashChanged = this->PrepareTerrain(terrainDirPath);
|
|
|
|
if (this->dataPtr->useTerrainPaging)
|
|
{
|
|
if (this->dataPtr->terrainHashChanged)
|
|
{
|
|
// Split the terrain. Every subterrain will be saved on disk and paged
|
|
this->SplitHeights(this->dataPtr->heights, nTerrains,
|
|
this->dataPtr->subTerrains);
|
|
}
|
|
|
|
this->dataPtr->pageManager = OGRE_NEW Ogre::PageManager();
|
|
this->dataPtr->pageManager->setPageProvider(
|
|
&this->dataPtr->dummyPageProvider);
|
|
|
|
// Add cameras
|
|
for (unsigned int i = 0; i < this->dataPtr->scene->CameraCount(); ++i)
|
|
{
|
|
this->dataPtr->pageManager->addCamera(
|
|
this->dataPtr->scene->GetCamera(i)->OgreCamera());
|
|
}
|
|
for (unsigned int i = 0; i < this->dataPtr->scene->UserCameraCount();
|
|
++i)
|
|
{
|
|
this->dataPtr->pageManager->addCamera(
|
|
this->dataPtr->scene->GetUserCamera(i)->OgreCamera());
|
|
}
|
|
|
|
this->dataPtr->terrainPaging =
|
|
OGRE_NEW Ogre::TerrainPaging(this->dataPtr->pageManager);
|
|
this->dataPtr->world = this->dataPtr->pageManager->createWorld();
|
|
this->dataPtr->terrainPaging->createWorldSection(
|
|
this->dataPtr->world, this->dataPtr->terrainGroup,
|
|
this->dataPtr->loadRadiusFactor * this->dataPtr->terrainSize.X(),
|
|
this->dataPtr->holdRadiusFactor * this->dataPtr->terrainSize.X(),
|
|
0, 0, sqrtN - 1, sqrtN - 1);
|
|
}
|
|
|
|
gzmsg << "Loading heightmap: " << terrainName.string() << std::endl;
|
|
common::Time time = common::Time::GetWallTime();
|
|
|
|
for (int y = 0; y <= sqrtN - 1; ++y)
|
|
for (int x = 0; x <= sqrtN - 1; ++x)
|
|
this->DefineTerrain(x, y);
|
|
|
|
// use gazebo shaders
|
|
this->CreateMaterial();
|
|
|
|
// Sync load since we want everything in place when we start
|
|
this->dataPtr->terrainGroup->loadAllTerrains(true);
|
|
|
|
gzmsg << "Heightmap loaded. Process took: "
|
|
<< (common::Time::GetWallTime() - time).Double()
|
|
<< " seconds" << std::endl;
|
|
|
|
// Calculate blend maps
|
|
if (this->dataPtr->terrainsImported)
|
|
{
|
|
Ogre::TerrainGroup::TerrainIterator ti =
|
|
this->dataPtr->terrainGroup->getTerrainIterator();
|
|
while (ti.hasMoreElements())
|
|
{
|
|
Ogre::Terrain *t = ti.getNext()->instance;
|
|
this->InitBlendMaps(t);
|
|
}
|
|
}
|
|
|
|
this->dataPtr->terrainGroup->freeTemporaryResources();
|
|
|
|
// save the terrain once its loaded
|
|
if (this->dataPtr->terrainsImported)
|
|
{
|
|
this->dataPtr->connections.push_back(
|
|
event::Events::ConnectPreRender(
|
|
std::bind(&Heightmap::SaveHeightmap, this)));
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
void Heightmap::SaveHeightmap()
|
|
{
|
|
// Calculate blend maps
|
|
if (this->dataPtr->terrainsImported &&
|
|
!this->dataPtr->terrainGroup->isDerivedDataUpdateInProgress())
|
|
{
|
|
// saving an ogre terrain data file can take quite some time for large dems.
|
|
gzmsg << "Saving heightmap cache data to " << (this->dataPtr->gzPagingDir /
|
|
boost::filesystem::path(this->dataPtr->filename).stem()).string()
|
|
<< std::endl;
|
|
common::Time time = common::Time::GetWallTime();
|
|
|
|
this->dataPtr->terrainGroup->saveAllTerrains(true);
|
|
|
|
gzmsg << "Heightmap cache data saved. Process took: "
|
|
<< (common::Time::GetWallTime() - time).Double() << " seconds."
|
|
<< std::endl;
|
|
|
|
this->dataPtr->terrainsImported = false;
|
|
this->dataPtr->connections.clear();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
void Heightmap::ConfigureTerrainDefaults()
|
|
{
|
|
// Configure global
|
|
|
|
// MaxPixelError: Decides how precise our terrain is going to be.
|
|
// A lower number will mean a more accurate terrain, at the cost of
|
|
// performance (because of more vertices)
|
|
this->dataPtr->terrainGlobals->setMaxPixelError(this->dataPtr->maxPixelError);
|
|
|
|
// CompositeMapDistance: decides how far the Ogre terrain will render
|
|
// the lightmapped terrain.
|
|
this->dataPtr->terrainGlobals->setCompositeMapDistance(2000);
|
|
|
|
#if (OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR >= 8) || \
|
|
OGRE_VERSION_MAJOR > 1
|
|
// Vertex compression breaks anything, e.g. Gpu laser, that tries to build
|
|
// a depth map.
|
|
this->dataPtr->terrainGlobals->setUseVertexCompressionWhenAvailable(false);
|
|
#endif
|
|
|
|
// Get the first directional light
|
|
LightPtr directionalLight;
|
|
for (unsigned int i = 0; i < this->dataPtr->scene->LightCount(); ++i)
|
|
{
|
|
LightPtr light = this->dataPtr->scene->GetLight(i);
|
|
if (light->Type() == "directional")
|
|
{
|
|
directionalLight = light;
|
|
break;
|
|
}
|
|
}
|
|
|
|
this->dataPtr->terrainGlobals->setSkirtSize(this->dataPtr->skirtLength);
|
|
|
|
this->dataPtr->terrainGlobals->setCompositeMapAmbient(
|
|
this->dataPtr->scene->OgreSceneManager()->getAmbientLight());
|
|
|
|
// Important to set these so that the terrain knows what to use for
|
|
// derived (non-realtime) data
|
|
if (directionalLight)
|
|
{
|
|
this->dataPtr->terrainGlobals->setLightMapDirection(
|
|
Conversions::Convert(directionalLight->Direction()));
|
|
|
|
this->dataPtr->terrainGlobals->setCompositeMapDiffuse(
|
|
Conversions::Convert(directionalLight->DiffuseColor()));
|
|
}
|
|
else
|
|
{
|
|
this->dataPtr->terrainGlobals->setLightMapDirection(
|
|
Ogre::Vector3(0, 0, -1));
|
|
this->dataPtr->terrainGlobals->setCompositeMapDiffuse(
|
|
Ogre::ColourValue(.6, .6, .6, 1));
|
|
}
|
|
|
|
// Configure default import settings for if we use imported image
|
|
Ogre::Terrain::ImportData &defaultimp =
|
|
this->dataPtr->terrainGroup->getDefaultImportSettings();
|
|
|
|
defaultimp.terrainSize = this->dataPtr->dataSize;
|
|
defaultimp.worldSize = this->dataPtr->terrainSize.X();
|
|
|
|
defaultimp.inputScale = 1.0;
|
|
|
|
defaultimp.minBatchSize = 17;
|
|
defaultimp.maxBatchSize = 65;
|
|
|
|
// textures. The default material generator takes two materials per layer.
|
|
// 1. diffuse_specular - diffuse texture with a specular map in the
|
|
// alpha channel
|
|
// 2. normal_height - normal map with a height map in the alpha channel
|
|
{
|
|
// number of texture layers
|
|
defaultimp.layerList.resize(this->dataPtr->diffuseTextures.size());
|
|
|
|
// The worldSize decides how big each splat of textures will be.
|
|
// A smaller value will increase the resolution
|
|
for (unsigned int i = 0; i < this->dataPtr->diffuseTextures.size(); ++i)
|
|
{
|
|
defaultimp.layerList[i].worldSize = this->dataPtr->worldSizes[i];
|
|
defaultimp.layerList[i].textureNames.push_back(
|
|
this->dataPtr->diffuseTextures[i]);
|
|
defaultimp.layerList[i].textureNames.push_back(
|
|
this->dataPtr->normalTextures[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void Heightmap::SetWireframe(const bool _show)
|
|
{
|
|
Ogre::TerrainGroup::TerrainIterator ti =
|
|
this->dataPtr->terrainGroup->getTerrainIterator();
|
|
while (ti.hasMoreElements())
|
|
{
|
|
Ogre::Terrain *terrain = ti.getNext()->instance;
|
|
GZ_ASSERT(terrain != nullptr, "Unable to get a valid terrain pointer");
|
|
|
|
Ogre::Material *material = terrain->getMaterial().get();
|
|
|
|
unsigned int techniqueCount, passCount;
|
|
Ogre::Technique *technique;
|
|
Ogre::Pass *pass;
|
|
|
|
for (techniqueCount = 0; techniqueCount < material->getNumTechniques();
|
|
++techniqueCount)
|
|
{
|
|
technique = material->getTechnique(techniqueCount);
|
|
|
|
for (passCount = 0; passCount < technique->getNumPasses(); ++passCount)
|
|
{
|
|
pass = technique->getPass(passCount);
|
|
if (_show)
|
|
pass->setPolygonMode(Ogre::PM_WIREFRAME);
|
|
else
|
|
pass->setPolygonMode(Ogre::PM_SOLID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void Heightmap::DefineTerrain(const int _x, const int _y)
|
|
{
|
|
Ogre::String filename = this->dataPtr->terrainGroup->generateFilename(_x, _y);
|
|
|
|
bool resourceExists =
|
|
Ogre::ResourceGroupManager::getSingleton().resourceExists(
|
|
this->dataPtr->terrainGroup->getResourceGroup(), filename);
|
|
|
|
if (resourceExists && !this->dataPtr->terrainHashChanged)
|
|
{
|
|
gzmsg << "Loading heightmap cache data: " << filename << std::endl;
|
|
|
|
this->dataPtr->terrainGroup->defineTerrain(_x, _y);
|
|
this->dataPtr->terrainsImported = false;
|
|
}
|
|
else
|
|
{
|
|
if (this->dataPtr->splitTerrain)
|
|
{
|
|
// generate the subterrains if needed
|
|
if (this->dataPtr->subTerrains.empty())
|
|
{
|
|
this->SplitHeights(this->dataPtr->heights,
|
|
this->dataPtr->numTerrainSubdivisions,
|
|
this->dataPtr->subTerrains);
|
|
}
|
|
|
|
this->dataPtr->terrainGroup->defineTerrain(_x, _y,
|
|
&this->dataPtr->subTerrains[this->dataPtr->terrainIdx][0]);
|
|
++this->dataPtr->terrainIdx;
|
|
}
|
|
else
|
|
{
|
|
this->dataPtr->terrainGroup->defineTerrain(_x, _y,
|
|
&this->dataPtr->heights[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::InitBlendMaps(Ogre::Terrain *_terrain)
|
|
{
|
|
if (!_terrain)
|
|
{
|
|
gzerr << "Invalid terrain\n";
|
|
return false;
|
|
}
|
|
|
|
// no blending to be done if there's only one texture or no textures at all.
|
|
if (this->dataPtr->blendHeight.size() <= 1u ||
|
|
this->dataPtr->diffuseTextures.size() <= 1u)
|
|
return false;
|
|
|
|
// Bounds check for following loop
|
|
if (_terrain->getLayerCount() < this->dataPtr->blendHeight.size() + 1)
|
|
{
|
|
gzerr << "Invalid terrain, too few layers to initialize blend map\n";
|
|
return false;
|
|
}
|
|
|
|
Ogre::Real val, height;
|
|
unsigned int i = 0;
|
|
|
|
std::vector<Ogre::TerrainLayerBlendMap *> blendMaps;
|
|
std::vector<float*> pBlend;
|
|
|
|
// Create the blend maps
|
|
for (i = 0; i < this->dataPtr->blendHeight.size(); ++i)
|
|
{
|
|
blendMaps.push_back(_terrain->getLayerBlendMap(i+1));
|
|
pBlend.push_back(blendMaps[i]->getBlendPointer());
|
|
}
|
|
|
|
// Set the blend values based on the height of the terrain
|
|
for (Ogre::uint16 y = 0; y < _terrain->getLayerBlendMapSize(); ++y)
|
|
{
|
|
for (Ogre::uint16 x = 0; x < _terrain->getLayerBlendMapSize(); ++x)
|
|
{
|
|
Ogre::Real tx, ty;
|
|
|
|
blendMaps[0]->convertImageToTerrainSpace(x, y, &tx, &ty);
|
|
height = _terrain->getHeightAtTerrainPosition(tx, ty);
|
|
|
|
for (i = 0; i < this->dataPtr->blendHeight.size(); ++i)
|
|
{
|
|
val = (height - this->dataPtr->blendHeight[i]) /
|
|
this->dataPtr->blendFade[i];
|
|
val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
|
|
*pBlend[i]++ = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure the blend maps are properly updated
|
|
for (i = 0; i < blendMaps.size(); ++i)
|
|
{
|
|
blendMaps[i]->dirty();
|
|
blendMaps[i]->update();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
double Heightmap::GetHeight(double _x, double _y, double _z)
|
|
{
|
|
return this->Height(_x, _y, _z);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
double Heightmap::Height(const double _x, const double _y, const double _z)
|
|
const
|
|
{
|
|
GZ_ASSERT(this->dataPtr->terrainGroup, "TerrainGroup pointer is NULL");
|
|
|
|
Ogre::TerrainGroup::RayResult result =
|
|
this->dataPtr->terrainGroup->rayIntersects(
|
|
Ogre::Ray(Ogre::Vector3(_x, _y, _z), Ogre::Vector3(0, 0, -1)));
|
|
|
|
if (result.hit)
|
|
return result.position.z;
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
Ogre::TerrainGroup::RayResult Heightmap::GetMouseHit(CameraPtr _camera,
|
|
math::Vector2i _mousePos)
|
|
{
|
|
return this->MouseHit(_camera, _mousePos.Ign());
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
Ogre::TerrainGroup::RayResult Heightmap::MouseHit(CameraPtr _camera,
|
|
const ignition::math::Vector2i &_mousePos) const
|
|
{
|
|
Ogre::Ray mouseRay = _camera->OgreCamera()->getCameraToViewportRay(
|
|
static_cast<float>(_mousePos.X()) /
|
|
_camera->OgreViewport()->getActualWidth(),
|
|
static_cast<float>(_mousePos.Y()) /
|
|
_camera->OgreViewport()->getActualHeight());
|
|
|
|
// The terrain uses a special ray intersection test.
|
|
return this->dataPtr->terrainGroup->rayIntersects(mouseRay);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::Smooth(CameraPtr _camera, math::Vector2i _mousePos,
|
|
double _outsideRadius, double _insideRadius,
|
|
double _weight)
|
|
{
|
|
return this->Smooth(_camera, _mousePos.Ign(), _outsideRadius, _insideRadius,
|
|
_weight);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::Smooth(CameraPtr _camera,
|
|
const ignition::math::Vector2i &_mousePos,
|
|
const double _outsideRadius, const double _insideRadius,
|
|
const double _weight)
|
|
{
|
|
Ogre::TerrainGroup::RayResult terrainResult =
|
|
this->MouseHit(_camera, _mousePos);
|
|
|
|
if (terrainResult.hit)
|
|
this->ModifyTerrain(terrainResult.position, _outsideRadius, _insideRadius,
|
|
_weight, "smooth");
|
|
|
|
return terrainResult.hit;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::Flatten(CameraPtr _camera, math::Vector2i _mousePos,
|
|
double _outsideRadius, double _insideRadius,
|
|
double _weight)
|
|
{
|
|
return this->Flatten(_camera, _mousePos.Ign(), _outsideRadius, _insideRadius,
|
|
_weight);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::Flatten(CameraPtr _camera,
|
|
const ignition::math::Vector2i &_mousePos,
|
|
const double _outsideRadius, const double _insideRadius,
|
|
const double _weight)
|
|
{
|
|
Ogre::TerrainGroup::RayResult terrainResult =
|
|
this->MouseHit(_camera, _mousePos);
|
|
|
|
if (terrainResult.hit)
|
|
this->ModifyTerrain(terrainResult.position, _outsideRadius,
|
|
_insideRadius, _weight, "flatten");
|
|
|
|
return terrainResult.hit;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::Raise(CameraPtr _camera, math::Vector2i _mousePos,
|
|
double _outsideRadius, double _insideRadius, double _weight)
|
|
{
|
|
return this->Raise(_camera, _mousePos.Ign(), _outsideRadius, _insideRadius,
|
|
_weight);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::Raise(CameraPtr _camera,
|
|
const ignition::math::Vector2i &_mousePos,
|
|
const double _outsideRadius, const double _insideRadius,
|
|
const double _weight)
|
|
{
|
|
// The terrain uses a special ray intersection test.
|
|
Ogre::TerrainGroup::RayResult terrainResult =
|
|
this->MouseHit(_camera, _mousePos);
|
|
|
|
if (terrainResult.hit)
|
|
this->ModifyTerrain(terrainResult.position, _outsideRadius,
|
|
_insideRadius, _weight, "raise");
|
|
|
|
return terrainResult.hit;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::Lower(CameraPtr _camera, math::Vector2i _mousePos,
|
|
double _outsideRadius, double _insideRadius, double _weight)
|
|
{
|
|
return this->Lower(_camera, _mousePos.Ign(), _outsideRadius, _insideRadius,
|
|
_weight);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool Heightmap::Lower(CameraPtr _camera,
|
|
const ignition::math::Vector2i &_mousePos,
|
|
const double _outsideRadius, const double _insideRadius,
|
|
const double _weight)
|
|
{
|
|
// The terrain uses a special ray intersection test.
|
|
Ogre::TerrainGroup::RayResult terrainResult =
|
|
this->MouseHit(_camera, _mousePos);
|
|
|
|
if (terrainResult.hit)
|
|
this->ModifyTerrain(terrainResult.position, _outsideRadius,
|
|
_insideRadius, _weight, "lower");
|
|
|
|
return terrainResult.hit;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
double Heightmap::GetAvgHeight(Ogre::Vector3 _pos, double _radius)
|
|
{
|
|
return this->AvgHeight(Conversions::ConvertIgn(_pos), _radius);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
double Heightmap::AvgHeight(const ignition::math::Vector3d &_pos,
|
|
const double _radius) const
|
|
{
|
|
GZ_ASSERT(this->dataPtr->terrainGroup, "TerrainGroup pointer is NULL");
|
|
Ogre::Terrain *terrain = this->dataPtr->terrainGroup->getTerrain(0, 0);
|
|
|
|
if (!terrain)
|
|
{
|
|
gzerr << "Invalid heightmap position [" << _pos << "]\n";
|
|
return 0.0;
|
|
}
|
|
|
|
int size = static_cast<int>(terrain->getSize());
|
|
|
|
Ogre::Vector3 pos;
|
|
terrain->getTerrainPosition(Conversions::Convert(_pos), &pos);
|
|
|
|
int startx = (pos.x - _radius) * size;
|
|
int starty = (pos.y - _radius) * size;
|
|
int endx = (pos.x + _radius) * size;
|
|
int endy = (pos.y + _radius) * size;
|
|
|
|
startx = std::max(startx, 0);
|
|
starty = std::max(starty, 0);
|
|
|
|
endx = std::min(endx, size);
|
|
endy = std::min(endy, size);
|
|
|
|
double sum = 0.0;
|
|
int count = 0;
|
|
for (int y = starty; y <= endy; ++y)
|
|
{
|
|
for (int x = startx; x <= endx; ++x)
|
|
{
|
|
sum += terrain->getHeightAtPoint(x, y);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return sum / count;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void Heightmap::ModifyTerrain(Ogre::Vector3 _pos, const double _outsideRadius,
|
|
const double _insideRadius, const double _weight, const std::string &_op)
|
|
{
|
|
GZ_ASSERT(this->dataPtr->terrainGroup, "TerrainGroup pointer is NULL");
|
|
Ogre::Terrain *terrain = this->dataPtr->terrainGroup->getTerrain(0, 0);
|
|
|
|
if (!terrain)
|
|
{
|
|
gzerr << "Invalid heightmap position [" << _pos << "]\n";
|
|
return;
|
|
}
|
|
|
|
int size = static_cast<int>(terrain->getSize());
|
|
|
|
Ogre::Vector3 pos;
|
|
terrain->getTerrainPosition(_pos, &pos);
|
|
|
|
int startx = (pos.x - _outsideRadius) * size;
|
|
int starty = (pos.y - _outsideRadius) * size;
|
|
int endx = (pos.x + _outsideRadius) * size;
|
|
int endy = (pos.y + _outsideRadius) * size;
|
|
|
|
startx = std::max(startx, 0);
|
|
starty = std::max(starty, 0);
|
|
|
|
endx = std::min(endx, size);
|
|
endy = std::min(endy, size);
|
|
|
|
double avgHeight = 0;
|
|
|
|
if (_op == "flatten" || _op == "smooth")
|
|
avgHeight = this->AvgHeight(Conversions::ConvertIgn(pos), _outsideRadius);
|
|
|
|
for (int y = starty; y <= endy; ++y)
|
|
{
|
|
for (int x = startx; x <= endx; ++x)
|
|
{
|
|
double tsXdist = (x / static_cast<double>(size)) - pos.x;
|
|
double tsYdist = (y / static_cast<double>(size)) - pos.y;
|
|
|
|
double weight = 1.0;
|
|
double dist = sqrt(tsYdist * tsYdist + tsXdist * tsXdist);
|
|
|
|
if (dist > _insideRadius)
|
|
{
|
|
weight = ignition::math::clamp(dist / _outsideRadius, 0.0, 1.0);
|
|
weight = 1.0 - (weight * weight);
|
|
}
|
|
|
|
float addedHeight = weight * _weight;
|
|
float newHeight = terrain->getHeightAtPoint(x, y);
|
|
|
|
if (_op == "raise")
|
|
newHeight += addedHeight;
|
|
else if (_op == "lower")
|
|
newHeight -= addedHeight;
|
|
else if (_op == "flatten")
|
|
{
|
|
if (newHeight < avgHeight)
|
|
newHeight += addedHeight;
|
|
else
|
|
newHeight -= addedHeight;
|
|
}
|
|
else if (_op == "smooth")
|
|
{
|
|
if (newHeight < avgHeight)
|
|
newHeight += addedHeight;
|
|
else
|
|
newHeight -= addedHeight;
|
|
}
|
|
else
|
|
gzerr << "Unknown terrain operation[" << _op << "]\n";
|
|
|
|
terrain->setHeightAtPoint(x, y, newHeight);
|
|
}
|
|
}
|
|
terrain->dirty();
|
|
terrain->update();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void Heightmap::SetupShadows(bool _enableShadows)
|
|
{
|
|
// Assume we get a shader model 2 material profile
|
|
Ogre::TerrainMaterialGeneratorA::SM2Profile *matProfile;
|
|
|
|
Ogre::TerrainMaterialGeneratorPtr matGen =
|
|
this->dataPtr->terrainGlobals->getDefaultMaterialGenerator();
|
|
|
|
matProfile = static_cast<GzTerrainMatGen::SM2Profile*>(
|
|
matGen->getActiveProfile());
|
|
if (!matProfile)
|
|
{
|
|
// using custom material script so ignore setting shadows
|
|
return;
|
|
}
|
|
|
|
matProfile->setLayerParallaxMappingEnabled(false);
|
|
|
|
if (_enableShadows)
|
|
{
|
|
// Make sure PSSM is already setup
|
|
matProfile->setReceiveDynamicShadowsEnabled(true);
|
|
matProfile->setReceiveDynamicShadowsPSSM(
|
|
RTShaderSystem::Instance()->GetPSSMShadowCameraSetup());
|
|
matProfile->setReceiveDynamicShadowsDepth(true);
|
|
matProfile->setReceiveDynamicShadowsLowLod(false);
|
|
}
|
|
else
|
|
{
|
|
matProfile->setReceiveDynamicShadowsPSSM(nullptr);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void Heightmap::SetLOD(const unsigned int _value)
|
|
{
|
|
this->dataPtr->maxPixelError = _value;
|
|
if (this->dataPtr->terrainGlobals)
|
|
{
|
|
this->dataPtr->terrainGlobals->setMaxPixelError(
|
|
this->dataPtr->maxPixelError);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
unsigned int Heightmap::LOD() const
|
|
{
|
|
return static_cast<unsigned int>(this->dataPtr->maxPixelError);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void Heightmap::SetSkirtLength(const double _value)
|
|
{
|
|
this->dataPtr->skirtLength = _value;
|
|
if (this->dataPtr->terrainGlobals)
|
|
{
|
|
this->dataPtr->terrainGlobals->setSkirtSize(
|
|
this->dataPtr->skirtLength);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
double Heightmap::SkirtLength() const
|
|
{
|
|
return this->dataPtr->skirtLength;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void Heightmap::SetMaterial(const std::string &_materialName)
|
|
{
|
|
this->dataPtr->materialName = _materialName;
|
|
if (!this->dataPtr->materialName.empty() && this->dataPtr->terrainGlobals)
|
|
this->CreateMaterial();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
std::string Heightmap::MaterialName() const
|
|
{
|
|
return this->dataPtr->materialName;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void Heightmap::CreateMaterial()
|
|
{
|
|
if (!this->dataPtr->materialName.empty())
|
|
{
|
|
// init custom material generator
|
|
Ogre::TerrainMaterialGeneratorPtr terrainMaterialGenerator;
|
|
TerrainMaterial *terrainMaterial = OGRE_NEW TerrainMaterial(
|
|
this->dataPtr->materialName);
|
|
if (this->dataPtr->splitTerrain)
|
|
terrainMaterial->setGridSize(this->dataPtr->numTerrainSubdivisions);
|
|
terrainMaterialGenerator.bind(terrainMaterial);
|
|
this->dataPtr->terrainGlobals->setDefaultMaterialGenerator(
|
|
terrainMaterialGenerator);
|
|
}
|
|
else
|
|
{
|
|
// use default material
|
|
// RTSS PSSM shadows compatible terrain material
|
|
if (!this->dataPtr->gzMatGen)
|
|
this->dataPtr->gzMatGen = new GzTerrainMatGen();
|
|
|
|
Ogre::TerrainMaterialGeneratorPtr ptr = Ogre::TerrainMaterialGeneratorPtr();
|
|
ptr.bind(this->dataPtr->gzMatGen);
|
|
|
|
this->dataPtr->terrainGlobals->setDefaultMaterialGenerator(ptr);
|
|
|
|
this->SetupShadows(true);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
unsigned int Heightmap::GetTerrainSubdivisionCount() const
|
|
{
|
|
return this->TerrainSubdivisionCount();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
unsigned int Heightmap::TerrainSubdivisionCount() const
|
|
{
|
|
return this->dataPtr->numTerrainSubdivisions;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////
|
|
// GzTerrainMatGen
|
|
/////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
GzTerrainMatGen::GzTerrainMatGen()
|
|
: TerrainMaterialGeneratorA()
|
|
{
|
|
/// \TODO - This will have to be changed if TerrainMaterialGeneratorA
|
|
/// ever supports more profiles than only CG
|
|
|
|
// Add custom SM2Profile SPAM
|
|
this->mProfiles.clear();
|
|
|
|
this->mProfiles.push_back(OGRE_NEW SM2Profile(this, "SM2",
|
|
"Profile for rendering on Shader Model 2 capable cards "
|
|
"(RTSS depth shadows compatible)"));
|
|
|
|
/// \TODO - check hardware capabilities & use fallbacks if required
|
|
/// (more profiles needed)
|
|
this->setActiveProfile(this->mProfiles[0]);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
GzTerrainMatGen::~GzTerrainMatGen()
|
|
{
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
GzTerrainMatGen::SM2Profile::SM2Profile(
|
|
Ogre::TerrainMaterialGenerator *_parent, const Ogre::String &_name,
|
|
const Ogre::String &_desc)
|
|
: TerrainMaterialGeneratorA::SM2Profile(_parent, _name, _desc)
|
|
{
|
|
this->mShaderGen = nullptr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
GzTerrainMatGen::SM2Profile::~SM2Profile()
|
|
{
|
|
// Because the base SM2Profile has no virtual destructor:
|
|
delete this->mShaderGen;
|
|
this->mShaderGen = nullptr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::addTechnique(
|
|
const Ogre::MaterialPtr &_mat, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt)
|
|
{
|
|
// Initiate specialized mShaderGen
|
|
// Ogre::GpuProgramManager &gmgr = Ogre::GpuProgramManager::getSingleton();
|
|
|
|
Ogre::HighLevelGpuProgramManager &hmgr =
|
|
Ogre::HighLevelGpuProgramManager::getSingleton();
|
|
|
|
if (!this->mShaderGen)
|
|
{
|
|
// By default we use the GLSL shaders.
|
|
if (hmgr.isLanguageSupported("glsl"))
|
|
{
|
|
this->mShaderGen = OGRE_NEW
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperGLSL();
|
|
}
|
|
else
|
|
{
|
|
gzthrow("No supported shader languages");
|
|
}
|
|
|
|
// Uncomment this to use cg shaders. I'm keeping the CG
|
|
// shader for reference. There is some more code to switch, located
|
|
// below, to enable CG shaders.
|
|
// if (hmgr.isLanguageSupported("cg"))
|
|
// {
|
|
// this->mShaderGen = OGRE_NEW
|
|
// // This will use Ogre's CG shader
|
|
// // Ogre::TerrainMaterialGeneratorA::SM2Profile::ShaderHelperCg();
|
|
// //
|
|
// // This will use our CG shader, which has terrain shadows
|
|
// GzTerrainMatGen::SM2Profile::ShaderHelperCg();
|
|
// }
|
|
|
|
// check SM3 features
|
|
this->mSM3Available =
|
|
Ogre::GpuProgramManager::getSingleton().isSyntaxSupported("ps_3_0");
|
|
|
|
#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8
|
|
this->mSM4Available =
|
|
Ogre::GpuProgramManager::getSingleton().isSyntaxSupported("ps_4_0");
|
|
#endif
|
|
}
|
|
|
|
// Unfortunately this doesn't work
|
|
// Default implementation
|
|
// TerrainMaterialGeneratorA::SM2Profile::addTechnique(mat, terrain, tt);
|
|
|
|
// So we have to replicate the entire method:
|
|
Ogre::Technique *tech = _mat->createTechnique();
|
|
|
|
// Only supporting one pass
|
|
Ogre::Pass *pass = tech->createPass();
|
|
|
|
// Doesn't delegate to the proper method otherwise
|
|
Ogre::HighLevelGpuProgramPtr vprog =
|
|
((GzTerrainMatGen::SM2Profile::ShaderHelperGLSL*)this->mShaderGen)
|
|
// Use this line if running Ogre's CG shaders
|
|
// ((TerrainMaterialGeneratorA::SM2Profile::ShaderHelperCg*)this->mShaderGen)
|
|
// Use this line if running our CG shaders
|
|
// ((GzTerrainMatGen::SM2Profile::ShaderHelperCg*)this->mShaderGen)
|
|
->generateVertexProgram(this, _terrain, _tt);
|
|
|
|
// DEBUG: std::cout << "VertShader[" << vprog->getName() << "]:\n"
|
|
// << vprog->getSource() << "\n\n";
|
|
|
|
Ogre::HighLevelGpuProgramPtr fprog =
|
|
((GzTerrainMatGen::SM2Profile::ShaderHelperGLSL*)this->mShaderGen)
|
|
// Use this line if running Ogre's CG shaders
|
|
// ((TerrainMaterialGeneratorA::SM2Profile::ShaderHelperCg*)this->mShaderGen)
|
|
// Use this line if running our CG shaders
|
|
// ((GzTerrainMatGen::SM2Profile::ShaderHelperCg*)this->mShaderGen)
|
|
->generateFragmentProgram(this, _terrain, _tt);
|
|
|
|
// DEBUG: std::cout << "FragShader[" << fprog->getName() << "]:\n"
|
|
// << fprog->getSource() << "\n\n";
|
|
|
|
pass->setVertexProgram(vprog->getName());
|
|
pass->setFragmentProgram(fprog->getName());
|
|
|
|
if (_tt == HIGH_LOD || _tt == RENDER_COMPOSITE_MAP)
|
|
{
|
|
// global normal map
|
|
Ogre::TextureUnitState* tu = pass->createTextureUnitState();
|
|
tu->setTextureName(_terrain->getTerrainNormalMap()->getName());
|
|
tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
|
|
|
// global colour map
|
|
if (_terrain->getGlobalColourMapEnabled() &&
|
|
this->isGlobalColourMapEnabled())
|
|
{
|
|
tu = pass->createTextureUnitState(
|
|
_terrain->getGlobalColourMap()->getName());
|
|
tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
|
}
|
|
|
|
// light map
|
|
if (this->isLightmapEnabled())
|
|
{
|
|
tu = pass->createTextureUnitState(_terrain->getLightmap()->getName());
|
|
tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
|
}
|
|
|
|
// blend maps
|
|
unsigned int maxLayers = this->getMaxLayers(_terrain);
|
|
|
|
unsigned int numBlendTextures = std::min(
|
|
_terrain->getBlendTextureCount(maxLayers),
|
|
_terrain->getBlendTextureCount());
|
|
|
|
unsigned int numLayers = std::min(
|
|
maxLayers, static_cast<unsigned int>(_terrain->getLayerCount()));
|
|
|
|
for (unsigned int i = 0; i < numBlendTextures; ++i)
|
|
{
|
|
tu = pass->createTextureUnitState(_terrain->getBlendTextureName(i));
|
|
tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
|
}
|
|
|
|
// layer textures
|
|
for (unsigned int i = 0; i < numLayers; ++i)
|
|
{
|
|
// diffuse / specular
|
|
pass->createTextureUnitState(_terrain->getLayerTextureName(i, 0));
|
|
|
|
// normal / height
|
|
pass->createTextureUnitState(_terrain->getLayerTextureName(i, 1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// LOW_LOD textures
|
|
// composite map
|
|
Ogre::TextureUnitState *tu = pass->createTextureUnitState();
|
|
tu->setTextureName(_terrain->getCompositeMap()->getName());
|
|
tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
|
}
|
|
|
|
// Add shadow textures (always at the end)
|
|
if (this->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
unsigned int numTextures = 1;
|
|
|
|
if (this->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = this->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
}
|
|
for (unsigned int i = 0; i < numTextures; ++i)
|
|
{
|
|
Ogre::TextureUnitState *tu = pass->createTextureUnitState();
|
|
tu->setContentType(Ogre::TextureUnitState::CONTENT_SHADOW);
|
|
tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_BORDER);
|
|
tu->setTextureBorderColour(Ogre::ColourValue::White);
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
// generate() and generateForCompositeMap() are identical to
|
|
// TerrainMaterialGeneratorA implementation, the only reason for repeating
|
|
// them is that, unfortunately, addTechnique() is not declared virtual.
|
|
Ogre::MaterialPtr GzTerrainMatGen::SM2Profile::generate(
|
|
const Ogre::Terrain *_terrain)
|
|
{
|
|
// re-use old material if exists
|
|
Ogre::MaterialPtr mat = _terrain->_getMaterial();
|
|
|
|
if (mat.isNull())
|
|
{
|
|
Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton();
|
|
|
|
// it's important that the names are deterministic for a given terrain, so
|
|
// use the terrain pointer as an ID
|
|
const Ogre::String &matName = _terrain->getMaterialName();
|
|
mat = matMgr.getByName(matName);
|
|
|
|
if (mat.isNull())
|
|
{
|
|
mat = matMgr.create(matName,
|
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
|
}
|
|
}
|
|
|
|
// clear everything
|
|
mat->removeAllTechniques();
|
|
|
|
// Automatically disable normal & parallax mapping if card cannot handle it
|
|
// We do this rather than having a specific technique for it since it's
|
|
// simpler.
|
|
Ogre::GpuProgramManager &gmgr = Ogre::GpuProgramManager::getSingleton();
|
|
|
|
if (!gmgr.isSyntaxSupported("ps_4_0") &&
|
|
!gmgr.isSyntaxSupported("ps_3_0") &&
|
|
!gmgr.isSyntaxSupported("ps_2_x") &&
|
|
!gmgr.isSyntaxSupported("fp40") &&
|
|
!gmgr.isSyntaxSupported("arbfp1"))
|
|
{
|
|
this->setLayerNormalMappingEnabled(false);
|
|
this->setLayerParallaxMappingEnabled(false);
|
|
}
|
|
|
|
this->addTechnique(mat, _terrain, HIGH_LOD);
|
|
|
|
// LOD
|
|
if (this->mCompositeMapEnabled)
|
|
{
|
|
this->addTechnique(mat, _terrain, LOW_LOD);
|
|
Ogre::Material::LodValueList lodValues;
|
|
lodValues.push_back(
|
|
Ogre::TerrainGlobalOptions::getSingleton().getCompositeMapDistance());
|
|
|
|
mat->setLodLevels(lodValues);
|
|
Ogre::Technique *lowLodTechnique = mat->getTechnique(1);
|
|
lowLodTechnique->setLodIndex(1);
|
|
}
|
|
|
|
this->UpdateParams(mat, _terrain);
|
|
|
|
return mat;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
Ogre::MaterialPtr GzTerrainMatGen::SM2Profile::generateForCompositeMap(
|
|
const Ogre::Terrain *_terrain)
|
|
{
|
|
// re-use old material if exists
|
|
Ogre::MaterialPtr mat = _terrain->_getCompositeMapMaterial();
|
|
|
|
if (mat.isNull())
|
|
{
|
|
Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton();
|
|
|
|
// it's important that the names are deterministic for a given terrain, so
|
|
// use the terrain pointer as an ID
|
|
const Ogre::String &matName = _terrain->getMaterialName() + "/comp";
|
|
|
|
mat = matMgr.getByName(matName);
|
|
|
|
if (mat.isNull())
|
|
{
|
|
mat = matMgr.create(matName,
|
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
|
}
|
|
}
|
|
|
|
// clear everything
|
|
mat->removeAllTechniques();
|
|
|
|
this->addTechnique(mat, _terrain, RENDER_COMPOSITE_MAP);
|
|
|
|
this->UpdateParamsForCompositeMap(mat, _terrain);
|
|
|
|
return mat;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::UpdateParams(const Ogre::MaterialPtr &_mat,
|
|
const Ogre::Terrain *_terrain)
|
|
{
|
|
static_cast<GzTerrainMatGen::SM2Profile::ShaderHelperGLSL*>(
|
|
this->mShaderGen)->updateParams(this, _mat, _terrain, false);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::UpdateParamsForCompositeMap(
|
|
const Ogre::MaterialPtr &_mat, const Ogre::Terrain *_terrain)
|
|
{
|
|
static_cast<GzTerrainMatGen::SM2Profile::ShaderHelperGLSL*>(
|
|
this->mShaderGen)->updateParams(this, _mat, _terrain, true);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////
|
|
// GLSL Shader helper
|
|
/////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////
|
|
|
|
/////////////////////////////////////////////////
|
|
Ogre::HighLevelGpuProgramPtr
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateVertexProgram(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt)
|
|
{
|
|
Ogre::HighLevelGpuProgramPtr ret =
|
|
this->createVertexProgram(_prof, _terrain, _tt);
|
|
|
|
Ogre::StringStream sourceStr;
|
|
this->generateVertexProgramSource(_prof, _terrain, _tt, sourceStr);
|
|
|
|
ret->setSource(sourceStr.str());
|
|
ret->load();
|
|
this->defaultVpParams(_prof, _terrain, _tt, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
Ogre::HighLevelGpuProgramPtr
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFragmentProgram(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain, TechniqueType _tt)
|
|
{
|
|
Ogre::HighLevelGpuProgramPtr ret = this->createFragmentProgram(_prof,
|
|
_terrain, _tt);
|
|
|
|
Ogre::StringStream sourceStr;
|
|
|
|
this->generateFragmentProgramSource(_prof, _terrain, _tt, sourceStr);
|
|
|
|
ret->setSource(sourceStr.str());
|
|
|
|
ret->load();
|
|
|
|
this->defaultFpParams(_prof, _terrain, _tt, ret);
|
|
|
|
Ogre::GpuProgramParametersSharedPtr params = ret->getDefaultParameters();
|
|
params->setIgnoreMissingParams(false);
|
|
|
|
Ogre::uint maxLayers = _prof->getMaxLayers(_terrain);
|
|
Ogre::uint numBlendTextures = std::min(
|
|
_terrain->getBlendTextureCount(maxLayers),
|
|
_terrain->getBlendTextureCount());
|
|
|
|
Ogre::uint numLayers = std::min(maxLayers,
|
|
static_cast<Ogre::uint>(_terrain->getLayerCount()));
|
|
|
|
int samplerCounter = 0;
|
|
|
|
if (_tt == LOW_LOD)
|
|
params->setNamedConstant("compositeMap", samplerCounter++);
|
|
else
|
|
{
|
|
params->setNamedConstant("globalNormal", samplerCounter++);
|
|
|
|
if (_terrain->getGlobalColourMapEnabled() &&
|
|
_prof->isGlobalColourMapEnabled())
|
|
{
|
|
params->setNamedConstant("globalColourMap", samplerCounter++);
|
|
}
|
|
|
|
if (_prof->isLightmapEnabled())
|
|
params->setNamedConstant("lightMap", samplerCounter++);
|
|
|
|
for (Ogre::uint i = 0; i < numBlendTextures; ++i)
|
|
{
|
|
params->setNamedConstant("blendTex" +
|
|
boost::lexical_cast<std::string>(i), samplerCounter++);
|
|
}
|
|
|
|
for (Ogre::uint i = 0; i < numLayers; ++i)
|
|
{
|
|
params->setNamedConstant("difftex" +
|
|
boost::lexical_cast<std::string>(i), samplerCounter++);
|
|
params->setNamedConstant("normtex" +
|
|
boost::lexical_cast<std::string>(i), samplerCounter++);
|
|
}
|
|
}
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
Ogre::uint numTextures = 1;
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
}
|
|
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
{
|
|
params->setNamedConstant("shadowMap" +
|
|
boost::lexical_cast<std::string>(i), samplerCounter++);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::updateParams(
|
|
const SM2Profile *_prof, const Ogre::MaterialPtr &_mat,
|
|
const Ogre::Terrain *_terrain, bool _compositeMap)
|
|
{
|
|
Ogre::Pass *p = _mat->getTechnique(0)->getPass(0);
|
|
|
|
if (_compositeMap)
|
|
{
|
|
this->updateVpParams(_prof, _terrain, RENDER_COMPOSITE_MAP,
|
|
p->getVertexProgramParameters());
|
|
this->updateFpParams(_prof, _terrain, RENDER_COMPOSITE_MAP,
|
|
p->getFragmentProgramParameters());
|
|
}
|
|
else
|
|
{
|
|
// high lod
|
|
this->updateVpParams(_prof, _terrain, HIGH_LOD,
|
|
p->getVertexProgramParameters());
|
|
this->updateFpParams(_prof, _terrain, HIGH_LOD,
|
|
p->getFragmentProgramParameters());
|
|
|
|
if (_prof->isCompositeMapEnabled())
|
|
{
|
|
// low lod
|
|
p = _mat->getTechnique(1)->getPass(0);
|
|
this->updateVpParams(_prof, _terrain, LOW_LOD,
|
|
p->getVertexProgramParameters());
|
|
this->updateFpParams(_prof, _terrain, LOW_LOD,
|
|
p->getFragmentProgramParameters());
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::
|
|
generateVertexProgramSource(const SM2Profile *_prof,
|
|
const Ogre::Terrain* _terrain, TechniqueType _tt,
|
|
Ogre::StringStream &_outStream)
|
|
{
|
|
this->generateVpHeader(_prof, _terrain, _tt, _outStream);
|
|
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
unsigned int maxLayers = _prof->getMaxLayers(_terrain);
|
|
unsigned int numLayers = std::min(maxLayers,
|
|
static_cast<unsigned int>(_terrain->getLayerCount()));
|
|
|
|
for (unsigned int i = 0; i < numLayers; ++i)
|
|
this->generateVpLayer(_prof, _terrain, _tt, i, _outStream);
|
|
}
|
|
|
|
this->generateVpFooter(_prof, _terrain, _tt, _outStream);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
// This method is identical to
|
|
// TerrainMaterialGeneratorA::SM2Profile::ShaderHelperGLSL::generateVpHeader()
|
|
// but is needed because generateVpDynamicShadowsParams() is not declared
|
|
// virtual.
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateVpHeader(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, Ogre::StringStream &_outStream)
|
|
{
|
|
bool compression = false;
|
|
|
|
_outStream << "#version " << glslVersion << "\n\n";
|
|
|
|
#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8
|
|
compression = _terrain->_getUseVertexCompression() &&
|
|
_tt != RENDER_COMPOSITE_MAP;
|
|
|
|
if (compression)
|
|
{
|
|
// The parameter "in vec4 vertex;" is automatically bound by OGRE.
|
|
// The parameter "in vec4 uv0'" is automatically bound by OGRE.
|
|
_outStream << vpInStr << " vec4 vertex;\n"
|
|
<< vpInStr << " vec4 uv0;\n";
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// The parameter "in vec4 vertex;" is automatically bound by OGRE.
|
|
// The parameter "in vec4 uv0'" is automatically bound by OGRE.
|
|
_outStream << vpInStr << " vec4 vertex;\n"
|
|
<< vpInStr << " vec4 uv0;\n";
|
|
}
|
|
|
|
if (_tt != RENDER_COMPOSITE_MAP)
|
|
// The parameter "in vec4 uv1'" is automatically bound by OGRE.
|
|
_outStream << vpInStr << " vec4 uv1;\n";
|
|
|
|
_outStream <<
|
|
"uniform mat4 worldMatrix;\n"
|
|
"uniform mat4 viewProjMatrix;\n"
|
|
"uniform vec2 lodMorph;\n";
|
|
|
|
if (compression)
|
|
{
|
|
_outStream <<
|
|
"uniform mat4 posIndexToObjectSpace;\n"
|
|
"uniform float baseUVScale;\n";
|
|
}
|
|
|
|
|
|
// uv multipliers
|
|
unsigned int maxLayers = _prof->getMaxLayers(_terrain);
|
|
unsigned int numLayers = std::min(maxLayers,
|
|
static_cast<unsigned int>(_terrain->getLayerCount()));
|
|
|
|
unsigned int numUVMultipliers = (numLayers / 4);
|
|
|
|
if (numLayers % 4)
|
|
++numUVMultipliers;
|
|
|
|
for (unsigned int i = 0; i < numUVMultipliers; ++i)
|
|
_outStream << "uniform vec4 uvMul" << i << ";\n";
|
|
|
|
_outStream <<
|
|
vpOutStr << " vec4 position;\n";
|
|
|
|
unsigned int texCoordSet = 1;
|
|
_outStream << vpOutStr << " vec4 uvMisc;\n";
|
|
|
|
// layer UV's premultiplied, packed as xy/zw
|
|
unsigned int numUVSets = numLayers / 2;
|
|
|
|
if (numLayers % 2)
|
|
++numUVSets;
|
|
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
for (unsigned int i = 0; i < numUVSets; ++i)
|
|
{
|
|
_outStream << vpOutStr << " vec4 layerUV" << i << ";\n";
|
|
}
|
|
}
|
|
|
|
if (_prof->getParent()->getDebugLevel() && _tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
_outStream << vpOutStr << " vec2 lodInfo;\n";
|
|
}
|
|
|
|
bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE &&
|
|
_tt != RENDER_COMPOSITE_MAP;
|
|
|
|
if (fog)
|
|
{
|
|
_outStream <<
|
|
"uniform vec4 fogParams;\n"
|
|
<< vpOutStr << " float fogVal;\n";
|
|
}
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
texCoordSet = this->generateVpDynamicShadowsParams(texCoordSet, _prof,
|
|
_terrain, _tt, _outStream);
|
|
}
|
|
|
|
// check we haven't exceeded texture coordinates
|
|
if (texCoordSet > 8)
|
|
{
|
|
OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS,
|
|
"Requested options require too many texture coordinate sets! "
|
|
"Try reducing the number of layers.",
|
|
__FUNCTION__);
|
|
}
|
|
|
|
_outStream << "void main()\n"
|
|
<< "{\n";
|
|
|
|
if (compression)
|
|
{
|
|
_outStream
|
|
<< " vec4 pos = posIndexToObjectSpace * "
|
|
<< "vec4(vertex.x, vertex.y, uv0.x, 1.0);\n"
|
|
|
|
<< " vec2 uv = vec2(vertex.x * baseUVScale, 1.0 - "
|
|
<< "(vertex.y * baseUVScale));\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream
|
|
<< " vec4 pos = vertex;\n"
|
|
<< " vec2 uv = vec2(uv0.x, uv0.y);\n";
|
|
}
|
|
|
|
_outStream << " vec4 worldPos = worldMatrix * pos;\n";
|
|
_outStream << " position = pos;\n";
|
|
|
|
if (_tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
// determine whether to apply the LOD morph to this vertex
|
|
// we store the deltas against all vertices so we only want to apply
|
|
// the morph to the ones which would disappear. The target LOD which is
|
|
// being morphed to is stored in lodMorph.y, and the LOD at which
|
|
// the vertex should be morphed is stored in uv.w. If we subtract
|
|
// the former from the latter, and arrange to only morph if the
|
|
// result is negative (it will only be -1 in fact, since after that
|
|
// the vertex will never be indexed), we will achieve our aim.
|
|
// sign(vertexLOD - targetLOD) == -1 is to morph
|
|
_outStream <<
|
|
" float toMorph = -min(0.0, sign(uv1.y - lodMorph.y));\n";
|
|
|
|
// this will either be 1 (morph) or 0 (don't morph)
|
|
if (_prof->getParent()->getDebugLevel())
|
|
{
|
|
// x == LOD level (-1 since value is target level, we want to
|
|
// display actual)
|
|
_outStream << "lodInfo.x = (lodMorph.y - 1.0) / "
|
|
<< _terrain->getNumLodLevels() << ";\n";
|
|
|
|
// y == LOD morph
|
|
_outStream << "lodInfo.y = toMorph * lodMorph.x;\n";
|
|
}
|
|
|
|
// morph
|
|
switch (_terrain->getAlignment())
|
|
{
|
|
case Ogre::Terrain::ALIGN_X_Y:
|
|
_outStream << " worldPos.z += uv1.x * toMorph * lodMorph.x;\n";
|
|
break;
|
|
case Ogre::Terrain::ALIGN_X_Z:
|
|
_outStream << " worldPos.y += uv1.x * toMorph * lodMorph.x;\n";
|
|
break;
|
|
case Ogre::Terrain::ALIGN_Y_Z:
|
|
_outStream << " worldPos.x += uv1.x * toMorph * lodMorph.x;\n";
|
|
break;
|
|
default:
|
|
gzerr << "Invalid alignment\n";
|
|
};
|
|
}
|
|
|
|
// generate UVs
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
for (unsigned int i = 0; i < numUVSets; ++i)
|
|
{
|
|
unsigned int layer = i * 2;
|
|
unsigned int uvMulIdx = layer / 4;
|
|
|
|
_outStream << " layerUV" << i << ".xy = " << " uv.xy * uvMul"
|
|
<< uvMulIdx << "." << this->GetChannel(layer) << ";\n";
|
|
_outStream << " layerUV" << i << ".zw = " << " uv.xy * uvMul"
|
|
<< uvMulIdx << "." << this->GetChannel(layer+1) << ";\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
// This method is identical to
|
|
// TerrainMaterialGeneratorA::SM2Profile::ShaderHelperGLSL::generateVpFooter()
|
|
// but is needed because generateVpDynamicShadows() is not declared virtual.
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateVpFooter(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, Ogre::StringStream &_outStream)
|
|
{
|
|
_outStream << " gl_Position = viewProjMatrix * worldPos;\n"
|
|
<< " uvMisc.xy = uv.xy;\n";
|
|
|
|
bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE &&
|
|
_tt != RENDER_COMPOSITE_MAP;
|
|
if (fog)
|
|
{
|
|
if (_terrain->getSceneManager()->getFogMode() == Ogre::FOG_LINEAR)
|
|
{
|
|
_outStream <<
|
|
" fogVal = clamp((oPos.z - fogParams.y) * fogParams.w, 0.0, 1.0);\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream <<
|
|
" fogVal = 1 - clamp(1 / (exp(oPos.z * fogParams.x)), 0.0, 1.0);\n";
|
|
}
|
|
}
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
this->generateVpDynamicShadows(_prof, _terrain, _tt, _outStream);
|
|
|
|
_outStream << "}\n";
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateVpDynamicShadows(
|
|
const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/,
|
|
TechniqueType /*_tt*/, Ogre::StringStream &_outStream)
|
|
{
|
|
unsigned int numTextures = 1;
|
|
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
}
|
|
|
|
// Calculate the position of vertex in light space
|
|
for (unsigned int i = 0; i < numTextures; ++i)
|
|
{
|
|
_outStream << " lightSpacePos" << i << " = texViewProjMatrix"
|
|
<< i << " * worldPos;\n";
|
|
|
|
// Don't linearize depth range: RTSS PSSM implementation uses
|
|
// view-space depth
|
|
// if (prof->getReceiveDynamicShadowsDepth())
|
|
// {
|
|
// // make linear
|
|
// outStream << "lightSpacePos" << i << ".z = (lightSpacePos" << i
|
|
// << ".z - depthRange" << i << ".x) * depthRange" << i
|
|
// << ".w;\n";
|
|
// }
|
|
}
|
|
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
_outStream << " // pass cam depth\n uvMisc.z = gl_Position.z;\n";
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::defaultVpParams(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, const Ogre::HighLevelGpuProgramPtr &_prog)
|
|
{
|
|
Ogre::GpuProgramParametersSharedPtr params = _prog->getDefaultParameters();
|
|
params->setIgnoreMissingParams(true);
|
|
|
|
params->setNamedAutoConstant("worldMatrix",
|
|
Ogre::GpuProgramParameters::ACT_WORLD_MATRIX);
|
|
|
|
params->setNamedAutoConstant("viewProjMatrix",
|
|
Ogre::GpuProgramParameters::ACT_VIEWPROJ_MATRIX);
|
|
|
|
params->setNamedAutoConstant("lodMorph",
|
|
Ogre::GpuProgramParameters::ACT_CUSTOM,
|
|
Ogre::Terrain::LOD_MORPH_CUSTOM_PARAM);
|
|
|
|
params->setNamedAutoConstant("fogParams",
|
|
Ogre::GpuProgramParameters::ACT_FOG_PARAMS);
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
unsigned int numTextures = 1;
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
}
|
|
for (unsigned int i = 0; i < numTextures; ++i)
|
|
{
|
|
params->setNamedAutoConstant("texViewProjMatrix" +
|
|
Ogre::StringConverter::toString(i),
|
|
Ogre::GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i);
|
|
|
|
// Don't add depth range params
|
|
// if (prof->getReceiveDynamicShadowsDepth())
|
|
// {
|
|
// params->setNamedAutoConstant("depthRange" +
|
|
// Ogre::StringConverter::toString(i),
|
|
// Ogre::GpuProgramParameters::ACT_SHADOW_SCENE_DEPTH_RANGE, i);
|
|
// }
|
|
}
|
|
}
|
|
|
|
#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8
|
|
if (_terrain->_getUseVertexCompression() && _tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
Ogre::Matrix4 posIndexToObjectSpace;
|
|
_terrain->getPointTransform(&posIndexToObjectSpace);
|
|
params->setNamedConstant("posIndexToObjectSpace", posIndexToObjectSpace);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
unsigned int GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::
|
|
generateVpDynamicShadowsParams(unsigned int _texCoord, const SM2Profile *_prof,
|
|
const Ogre::Terrain * /*_terrain*/, TechniqueType /*_tt*/,
|
|
Ogre::StringStream &_outStream)
|
|
{
|
|
// out semantics & params
|
|
unsigned int numTextures = 1;
|
|
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
}
|
|
|
|
for (unsigned int i = 0; i < numTextures; ++i)
|
|
{
|
|
_outStream << vpOutStr << " vec4 lightSpacePos" << i << ";\n"
|
|
<< "uniform mat4 texViewProjMatrix" << i << ";\n";
|
|
|
|
// Don't add depth range params
|
|
// if (prof->getReceiveDynamicShadowsDepth())
|
|
// {
|
|
// _outStream << ", uniform float4 depthRange" << i
|
|
// << " // x = min, y = max, z = range, w = 1/range\n";
|
|
// }
|
|
}
|
|
|
|
return _texCoord;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpHeader(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, Ogre::StringStream &_outStream)
|
|
{
|
|
_outStream << "#version " << glslVersion << "\n\n";
|
|
|
|
_outStream <<
|
|
"vec4 expand(vec4 v)\n"
|
|
"{\n"
|
|
" return v * 2 - 1;\n"
|
|
"}\n\n";
|
|
|
|
_outStream <<
|
|
"vec4 lit(float NdotL, float NdotH, float m)\n"
|
|
"{\n"
|
|
" float specular = (NdotL > 0) ? pow(max(0.0, NdotH), m) : 0.0;\n"
|
|
" return vec4(1.0, max(0.0, NdotL), specular, 1.0);\n"
|
|
"}\n";
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
this->generateFpDynamicShadowsHelpers(_prof, _terrain, _tt, _outStream);
|
|
|
|
_outStream <<
|
|
fpInStr << " vec4 position;\n";
|
|
|
|
Ogre::uint texCoordSet = 1;
|
|
_outStream << fpInStr << " vec4 uvMisc;\n";
|
|
|
|
// UV's premultiplied, packed as xy/zw
|
|
Ogre::uint maxLayers = _prof->getMaxLayers(_terrain);
|
|
Ogre::uint numBlendTextures = std::min(
|
|
_terrain->getBlendTextureCount(maxLayers),
|
|
_terrain->getBlendTextureCount());
|
|
Ogre::uint numLayers = std::min(maxLayers,
|
|
static_cast<Ogre::uint>(_terrain->getLayerCount()));
|
|
|
|
Ogre::uint numUVSets = numLayers / 2;
|
|
|
|
if (numLayers % 2)
|
|
++numUVSets;
|
|
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
for (Ogre::uint i = 0; i < numUVSets; ++i)
|
|
{
|
|
_outStream <<
|
|
fpInStr << " vec4 layerUV" << i << ";\n";
|
|
}
|
|
}
|
|
|
|
if (_prof->getParent()->getDebugLevel() && _tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
_outStream << fpInStr << " vec2 lodInfo;\n";
|
|
}
|
|
|
|
bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE &&
|
|
_tt != RENDER_COMPOSITE_MAP;
|
|
|
|
if (fog)
|
|
{
|
|
_outStream <<
|
|
"uniform vec3 fogColour;\n"
|
|
<< fpInStr << " float fogVal;\n";
|
|
}
|
|
|
|
Ogre::uint currentSamplerIdx = 0;
|
|
|
|
_outStream <<
|
|
// Only 1 light supported in this version
|
|
// deferred shading profile / generator later, ok? :)
|
|
"uniform vec3 ambient;\n"
|
|
"uniform vec4 lightPosObjSpace;\n"
|
|
"uniform vec3 lightDiffuseColour;\n"
|
|
"uniform vec3 lightSpecularColour;\n"
|
|
"uniform vec3 eyePosObjSpace;\n"
|
|
// pack scale, bias and specular
|
|
"uniform vec4 scaleBiasSpecular;\n";
|
|
|
|
if (_tt == LOW_LOD)
|
|
{
|
|
// single composite map covers all the others below
|
|
_outStream << "uniform sampler2D compositeMap;\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream << "uniform sampler2D globalNormal;\n";
|
|
|
|
if (_terrain->getGlobalColourMapEnabled() &&
|
|
_prof->isGlobalColourMapEnabled())
|
|
{
|
|
_outStream << "uniform sampler2D globalColourMap;\n";
|
|
}
|
|
|
|
if (_prof->isLightmapEnabled())
|
|
{
|
|
_outStream << "uniform sampler2D lightMap;\n";
|
|
}
|
|
|
|
// Blend textures - sampler definitions
|
|
for (Ogre::uint i = 0; i < numBlendTextures; ++i)
|
|
{
|
|
_outStream << "uniform sampler2D blendTex" << i << ";\n";
|
|
}
|
|
|
|
// Layer textures - sampler definitions & UV multipliers
|
|
for (Ogre::uint i = 0; i < numLayers; ++i)
|
|
{
|
|
_outStream << "uniform sampler2D difftex" << i << ";\n";
|
|
_outStream << "uniform sampler2D normtex" << i << ";\n";
|
|
}
|
|
}
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
this->generateFpDynamicShadowsParams(&texCoordSet, ¤tSamplerIdx,
|
|
_prof, _terrain, _tt, _outStream);
|
|
}
|
|
|
|
// check we haven't exceeded samplers
|
|
if (currentSamplerIdx > 16)
|
|
{
|
|
OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS,
|
|
"Requested options require too many texture samplers! "
|
|
"Try reducing the number of layers.", __FUNCTION__);
|
|
}
|
|
|
|
std::string outputColTypeStr = "vec4";
|
|
if (glslVersion != "120")
|
|
{
|
|
_outStream << "out vec4 outputCol;\n";
|
|
outputColTypeStr = "";
|
|
}
|
|
|
|
_outStream <<
|
|
"void main()\n"
|
|
"{\n"
|
|
" float shadow = 1.0;\n"
|
|
" vec2 uv = uvMisc.xy;\n"
|
|
" " << outputColTypeStr << " outputCol = vec4(0.0, 0.0, 0.0, 1.0);\n";
|
|
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
// global normal
|
|
_outStream << " vec3 normal = expand("
|
|
<< textureStr << "(globalNormal, uv)).xyz;\n";
|
|
}
|
|
|
|
_outStream <<
|
|
" vec3 lightDir =\n"
|
|
" lightPosObjSpace.xyz - (position.xyz * lightPosObjSpace.w);\n"
|
|
" vec3 eyeDir = eyePosObjSpace - position.xyz;\n"
|
|
|
|
// set up accumulation areas
|
|
" vec3 diffuse = vec3(0.0, 0.0, 0.0);\n"
|
|
" float specular = 0.0;\n";
|
|
|
|
if (_tt == LOW_LOD)
|
|
{
|
|
// we just do a single calculation from composite map
|
|
_outStream <<
|
|
" vec4 composite = " << textureStr << "(compositeMap, uv);\n"
|
|
" diffuse = composite.xyz;\n";
|
|
// TODO - specular; we'll need normals for this!
|
|
}
|
|
else
|
|
{
|
|
// set up the blend values
|
|
for (Ogre::uint i = 0; i < numBlendTextures; ++i)
|
|
{
|
|
_outStream << " vec4 blendTexVal" << i
|
|
<< " = " << textureStr << "(blendTex" << i << ", uv);\n";
|
|
}
|
|
|
|
if (_prof->isLayerNormalMappingEnabled())
|
|
{
|
|
// derive the tangent space basis
|
|
// we do this in the pixel shader because we don't have per-vertex normals
|
|
// because of the LOD, we use a normal map
|
|
// tangent is always +x or -z in object space depending on alignment
|
|
switch (_terrain->getAlignment())
|
|
{
|
|
case Ogre::Terrain::ALIGN_X_Y:
|
|
case Ogre::Terrain::ALIGN_X_Z:
|
|
_outStream << " vec3 tangent = vec3(1.0, 0.0, 0.0);\n";
|
|
break;
|
|
case Ogre::Terrain::ALIGN_Y_Z:
|
|
_outStream << " vec3 tangent = vec3(0.0, 0.0, -1.0);\n";
|
|
break;
|
|
default:
|
|
gzerr << "Invalid terrain alignment\n";
|
|
break;
|
|
};
|
|
|
|
_outStream << " vec3 binormal = normalize(cross(tangent, normal));\n";
|
|
// note, now we need to re-cross to derive tangent again because it
|
|
// wasn't orthonormal
|
|
_outStream << " tangent = normalize(cross(normal, binormal));\n";
|
|
// derive final matrix
|
|
/*_outStream << " mat3 TBN = mat3(tangent.x, tangent.y, tangent.z,"
|
|
"binormal.x, binormal.y, binormal.z,"
|
|
"normal.x, normal.y, normal.z);\n";
|
|
*/
|
|
|
|
// set up lighting result placeholders for interpolation
|
|
_outStream << " vec4 litRes, litResLayer;\n";
|
|
_outStream << " vec3 TSlightDir, TSeyeDir, TShalfAngle, TSnormal;\n";
|
|
if (_prof->isLayerParallaxMappingEnabled())
|
|
_outStream << " float displacement;\n";
|
|
// move
|
|
_outStream << " TSlightDir = normalize(vec3(dot(tangent, lightDir),"
|
|
"dot(binormal, lightDir),"
|
|
"dot(normal, lightDir)));\n";
|
|
_outStream << " TSeyeDir = normalize(vec3(dot(tangent, eyeDir),"
|
|
"dot(binormal, eyeDir),"
|
|
"dot(normal, eyeDir)));\n";
|
|
}
|
|
else
|
|
{
|
|
// simple per-pixel lighting with no normal mapping
|
|
_outStream << " lightDir = normalize(lightDir);\n";
|
|
_outStream << " eyeDir = normalize(eyeDir);\n";
|
|
_outStream << " vec3 halfAngle = normalize(lightDir + eyeDir);\n";
|
|
|
|
_outStream << " vec4 litRes = lit(dot(lightDir, normal), "
|
|
"dot(halfAngle, normal), scaleBiasSpecular.z);\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpDynamicShadowsParams(
|
|
Ogre::uint *_texCoord, Ogre::uint *_sampler, const SM2Profile *_prof,
|
|
const Ogre::Terrain * /*_terrain*/, TechniqueType _tt,
|
|
Ogre::StringStream &_outStream)
|
|
{
|
|
if (_tt == HIGH_LOD)
|
|
this->mShadowSamplerStartHi = *_sampler;
|
|
else if (_tt == LOW_LOD)
|
|
this->mShadowSamplerStartLo = *_sampler;
|
|
|
|
// in semantics & params
|
|
Ogre::uint numTextures = 1;
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
_outStream << "uniform vec4 pssmSplitPoints;\n";
|
|
}
|
|
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
{
|
|
_outStream << fpInStr <<
|
|
" vec4 lightSpacePos" << i << ";\n" <<
|
|
"uniform sampler2D shadowMap" << i << ";\n";
|
|
|
|
*_sampler = *_sampler + 1;
|
|
*_texCoord = *_texCoord + 1;
|
|
|
|
if (_prof->getReceiveDynamicShadowsDepth())
|
|
{
|
|
_outStream <<
|
|
"uniform float inverseShadowmapSize" << i << ";\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpLayer(
|
|
const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/,
|
|
TechniqueType _tt, Ogre::uint _layer,
|
|
Ogre::StringStream &_outStream)
|
|
{
|
|
Ogre::uint uvIdx = _layer / 2;
|
|
Ogre::String uvChannels = (_layer % 2) ? ".zw" : ".xy";
|
|
Ogre::uint blendIdx = (_layer-1) / 4;
|
|
Ogre::String blendChannel = this->GetChannel(_layer-1);
|
|
Ogre::String blendWeightStr = Ogre::String("blendTexVal") +
|
|
Ogre::StringConverter::toString(blendIdx) + "." + blendChannel;
|
|
|
|
// generate early-out conditional
|
|
// Disable - causing some issues even when trying to force the use of texldd
|
|
// if (layer && prof->_isSM3Available())
|
|
// _outStream << " if (" << blendWeightStr << " > 0.0003)\n {\n";
|
|
|
|
// generate UV
|
|
_outStream << " vec2 uv" << _layer << " = layerUV" << uvIdx
|
|
<< uvChannels << ";\n";
|
|
|
|
// calculate lighting here if normal mapping
|
|
if (_prof->isLayerNormalMappingEnabled())
|
|
{
|
|
if (_prof->isLayerParallaxMappingEnabled() && _tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
// modify UV - note we have to sample an extra time
|
|
_outStream << " displacement = " << textureStr << "(normtex" << _layer
|
|
<< ", uv" << _layer << ").w\n"
|
|
" * scaleBiasSpecular.x + scaleBiasSpecular.y;\n";
|
|
_outStream << " uv" << _layer << " += TSeyeDir.xy * displacement;\n";
|
|
}
|
|
|
|
// access TS normal map
|
|
_outStream << " TSnormal = expand(" << textureStr << "(normtex"
|
|
<< _layer << ", uv" << _layer << ")).xyz;\n";
|
|
_outStream << " TShalfAngle = normalize(TSlightDir + TSeyeDir);\n";
|
|
|
|
_outStream << " litResLayer = lit(dot(TSlightDir, TSnormal), "
|
|
"dot(TShalfAngle, TSnormal), scaleBiasSpecular.z);\n";
|
|
|
|
if (!_layer)
|
|
_outStream << " litRes = litResLayer;\n";
|
|
else
|
|
_outStream << " litRes = mix(litRes, litResLayer, "
|
|
<< blendWeightStr << ");\n";
|
|
}
|
|
|
|
// sample diffuse texture
|
|
_outStream << " vec4 diffuseSpecTex" << _layer
|
|
<< " = " << textureStr << "(difftex" << _layer << ", uv" << _layer
|
|
<< ");\n";
|
|
|
|
// apply to common
|
|
if (!_layer)
|
|
{
|
|
_outStream << " diffuse = diffuseSpecTex0.xyz;\n";
|
|
if (_prof->isLayerSpecularMappingEnabled())
|
|
_outStream << " specular = diffuseSpecTex0.w;\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream << " diffuse = mix(diffuse, diffuseSpecTex" << _layer
|
|
<< ".xyz, " << blendWeightStr << ");\n";
|
|
|
|
if (_prof->isLayerSpecularMappingEnabled())
|
|
{
|
|
_outStream << " specular = mix(specular, diffuseSpecTex" << _layer
|
|
<< ".w, " << blendWeightStr << ");\n";
|
|
}
|
|
}
|
|
|
|
// End early-out
|
|
// Disable - causing some issues even when trying to force the use of texldd
|
|
// if (layer && prof->_isSM3Available())
|
|
// _outStream << " } // early-out blend value\n";
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpFooter(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, Ogre::StringStream &_outStream)
|
|
{
|
|
if (_tt == LOW_LOD)
|
|
{
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
this->generateFpDynamicShadows(_prof, _terrain, _tt, _outStream);
|
|
_outStream << " outputCol.xyz = diffuse * rtshadow;\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream << " outputCol.xyz = diffuse;\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_terrain->getGlobalColourMapEnabled() &&
|
|
_prof->isGlobalColourMapEnabled())
|
|
{
|
|
// sample colour map and apply to diffuse
|
|
_outStream << " diffuse *= " << textureStr
|
|
<< "(globalColourMap, uv).xyz;\n";
|
|
}
|
|
|
|
if (_prof->isLightmapEnabled())
|
|
{
|
|
// sample lightmap
|
|
_outStream << " shadow = " << textureStr << "(lightMap, uv).x;\n";
|
|
}
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
this->generateFpDynamicShadows(_prof, _terrain, _tt, _outStream);
|
|
}
|
|
|
|
// diffuse lighting
|
|
_outStream << " outputCol.xyz += ambient * diffuse + litRes.y * "
|
|
"lightDiffuseColour * diffuse * shadow;\n";
|
|
|
|
// specular default
|
|
if (!_prof->isLayerSpecularMappingEnabled())
|
|
_outStream << " specular = 1.0;\n";
|
|
|
|
if (_tt == RENDER_COMPOSITE_MAP)
|
|
{
|
|
// Lighting embedded in alpha
|
|
_outStream << " outputCol.w = shadow;\n";
|
|
}
|
|
else
|
|
{
|
|
// Apply specular
|
|
_outStream << " outputCol.xyz += litRes.z * lightSpecularColour * "
|
|
"specular * shadow;\n";
|
|
|
|
if (_prof->getParent()->getDebugLevel())
|
|
{
|
|
_outStream << " outputCol.xy += lodInfo.xy;\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE &&
|
|
_tt != RENDER_COMPOSITE_MAP;
|
|
if (fog)
|
|
{
|
|
_outStream << " outputCol.xyz = mix(outputCol.xyz, fogColour, fogVal);\n";
|
|
}
|
|
|
|
if (glslVersion == "120")
|
|
_outStream << " gl_FragColor = outputCol;\n";
|
|
|
|
// Final return
|
|
_outStream << "\n}\n";
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpDynamicShadowsHelpers(
|
|
const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/,
|
|
TechniqueType /*_tt*/, Ogre::StringStream &_outStream)
|
|
{
|
|
// TODO make filtering configurable
|
|
_outStream <<
|
|
"// Simple PCF\n"
|
|
"// Number of samples in one dimension (square for total samples)\n"
|
|
"#define NUM_SHADOW_SAMPLES_1D 2.0\n"
|
|
"#define SHADOW_FILTER_SCALE 1.0\n"
|
|
|
|
"#define SHADOW_SAMPLES NUM_SHADOW_SAMPLES_1D*NUM_SHADOW_SAMPLES_1D\n"
|
|
|
|
"vec4 offsetSample(vec4 uv, vec2 offset, float invMapSize)\n"
|
|
"{\n"
|
|
" return vec4(uv.xy + offset * invMapSize * uv.w, uv.z, uv.w);\n"
|
|
"}\n";
|
|
|
|
if (_prof->getReceiveDynamicShadowsDepth())
|
|
{
|
|
_outStream <<
|
|
"float calcDepthShadow(sampler2D shadowMap, vec4 uv, "
|
|
"float invShadowMapSize)\n"
|
|
"{\n"
|
|
" // 4-sample PCF\n"
|
|
" float shadow = 0.0;\n"
|
|
" float offset = (NUM_SHADOW_SAMPLES_1D/2.0 - 0.5) *SHADOW_FILTER_SCALE;"
|
|
"\n"
|
|
" for (float y = -offset; y <= offset; y += SHADOW_FILTER_SCALE)\n"
|
|
" for (float x = -offset; x <= offset; x += SHADOW_FILTER_SCALE)\n"
|
|
" {\n"
|
|
" vec4 newUV = offsetSample(uv, vec2(x, y), invShadowMapSize);\n"
|
|
" // manually project and assign derivatives\n"
|
|
" // to avoid gradient issues inside loops\n"
|
|
" newUV = newUV / newUV.w;\n";
|
|
// The following line used to be:
|
|
// " float depth = tex2d(shadowMap, newUV.xy).x;\n"
|
|
if (glslVersion == "120")
|
|
_outStream <<
|
|
" float depth = texture2D(shadowMap, newUV.xy).x;\n";
|
|
else
|
|
{
|
|
_outStream <<
|
|
" float depth = textureGrad(shadowMap, newUV.xy, "
|
|
" vec2(1.0, 1.0), vec2(1.0, 1.0)).x;\n";
|
|
}
|
|
_outStream <<
|
|
// " if (depth >= 1.0 || depth >= uv.z)\n"
|
|
" if (depth >= 1.0 || depth >= newUV.z)\n"
|
|
" shadow += 1.0;\n"
|
|
" }\n"
|
|
" shadow /= (SHADOW_SAMPLES); \n"
|
|
" return shadow;\n"
|
|
"}\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream <<
|
|
"float calcSimpleShadow(sampler2D shadowMap, vec4 shadowMapPos)\n"
|
|
"{\n"
|
|
" return " << textureStr << "Proj(shadowMap, shadowMapPos).x;\n"
|
|
"}\n";
|
|
}
|
|
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
Ogre::uint numTextures =
|
|
_prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
|
|
if (_prof->getReceiveDynamicShadowsDepth())
|
|
{
|
|
_outStream << "float calcPSSMDepthShadow(";
|
|
}
|
|
else
|
|
{
|
|
_outStream << "float calcPSSMSimpleShadow(";
|
|
}
|
|
|
|
_outStream << "\n ";
|
|
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
_outStream << "sampler2D shadowMap" << i << ", ";
|
|
|
|
_outStream << "\n ";
|
|
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
_outStream << "vec4 lsPos" << i << ", ";
|
|
|
|
if (_prof->getReceiveDynamicShadowsDepth())
|
|
{
|
|
_outStream << "\n ";
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
_outStream << "float invShadowmapSize" << i << ", ";
|
|
}
|
|
|
|
_outStream << "\n"
|
|
" vec4 pssmSplitPoints, float camDepth)\n"
|
|
"{\n"
|
|
" float shadow = 1.0;\n"
|
|
" // calculate shadow\n";
|
|
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
{
|
|
if (!i)
|
|
{
|
|
_outStream << " if (camDepth <= pssmSplitPoints."
|
|
<< this->GetChannel(i) << ")\n";
|
|
}
|
|
else if (i < numTextures-1)
|
|
{
|
|
_outStream << " else if (camDepth <= pssmSplitPoints."
|
|
<< this->GetChannel(i) << ")\n";
|
|
}
|
|
else
|
|
_outStream << " else\n";
|
|
|
|
_outStream << " {\n";
|
|
|
|
if (_prof->getReceiveDynamicShadowsDepth())
|
|
{
|
|
_outStream << " shadow = calcDepthShadow(shadowMap" << i
|
|
<< ", lsPos" << i << ", invShadowmapSize" << i << ");\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream << " shadow = calcSimpleShadow(shadowMap" << i
|
|
<< ", lsPos" << i << ");\n";
|
|
}
|
|
_outStream << " }\n";
|
|
}
|
|
|
|
_outStream << " return shadow;\n"
|
|
"}\n\n\n";
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpDynamicShadows(
|
|
const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/,
|
|
TechniqueType /*_tt*/, Ogre::StringStream &_outStream)
|
|
{
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
Ogre::uint numTextures =
|
|
_prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
|
|
_outStream << " float camDepth = uvMisc.z;\n";
|
|
|
|
if (_prof->getReceiveDynamicShadowsDepth())
|
|
{
|
|
_outStream << " float rtshadow = calcPSSMDepthShadow(";
|
|
}
|
|
else
|
|
{
|
|
_outStream << " float rtshadow = calcPSSMSimpleShadow(";
|
|
}
|
|
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
_outStream << "shadowMap" << i << ", ";
|
|
|
|
_outStream << "\n ";
|
|
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
_outStream << "lightSpacePos" << i << ", ";
|
|
|
|
if (_prof->getReceiveDynamicShadowsDepth())
|
|
{
|
|
_outStream << "\n ";
|
|
|
|
for (Ogre::uint i = 0; i < numTextures; ++i)
|
|
_outStream << "inverseShadowmapSize" << i << ", ";
|
|
}
|
|
_outStream << "\n" <<
|
|
" pssmSplitPoints, camDepth);\n";
|
|
}
|
|
else
|
|
{
|
|
if (_prof->getReceiveDynamicShadowsDepth())
|
|
{
|
|
_outStream <<
|
|
" float rtshadow = calcDepthShadow(shadowMap0, lightSpacePos0, "
|
|
"inverseShadowmapSize0);";
|
|
}
|
|
else
|
|
{
|
|
_outStream <<
|
|
" float rtshadow = calcSimpleShadow(shadowMap0, lightSpacePos0);";
|
|
}
|
|
}
|
|
|
|
_outStream << " shadow = rtshadow;//min(shadow, rtshadow);\n";
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFragmentProgramSource(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, Ogre::StringStream &_outStream)
|
|
{
|
|
this->generateFpHeader(_prof, _terrain, _tt, _outStream);
|
|
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
Ogre::uint maxLayers = _prof->getMaxLayers(_terrain);
|
|
Ogre::uint numLayers = std::min(maxLayers,
|
|
static_cast<Ogre::uint>(_terrain->getLayerCount()));
|
|
|
|
for (Ogre::uint i = 0; i < numLayers; ++i)
|
|
this->generateFpLayer(_prof, _terrain, _tt, i, _outStream);
|
|
}
|
|
|
|
this->generateFpFooter(_prof, _terrain, _tt, _outStream);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::updateVpParams(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8
|
|
TechniqueType _tt,
|
|
#else
|
|
TechniqueType /*_tt*/,
|
|
#endif
|
|
const Ogre::GpuProgramParametersSharedPtr &_params)
|
|
{
|
|
_params->setIgnoreMissingParams(true);
|
|
Ogre::uint maxLayers = _prof->getMaxLayers(_terrain);
|
|
Ogre::uint numLayers = std::min(maxLayers,
|
|
static_cast<Ogre::uint>(_terrain->getLayerCount()));
|
|
|
|
Ogre::uint numUVMul = numLayers / 4;
|
|
|
|
if (numLayers % 4)
|
|
++numUVMul;
|
|
|
|
for (Ogre::uint i = 0; i < numUVMul; ++i)
|
|
{
|
|
Ogre::Vector4 uvMul(
|
|
_terrain->getLayerUVMultiplier(i * 4),
|
|
_terrain->getLayerUVMultiplier(i * 4 + 1),
|
|
_terrain->getLayerUVMultiplier(i * 4 + 2),
|
|
_terrain->getLayerUVMultiplier(i * 4 + 3));
|
|
_params->setNamedConstant("uvMul" +
|
|
Ogre::StringConverter::toString(i), uvMul);
|
|
}
|
|
|
|
#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8
|
|
if (_terrain->_getUseVertexCompression() && _tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
Ogre::Real baseUVScale = 1.0f / (_terrain->getSize() - 1);
|
|
_params->setNamedConstant("baseUVScale", baseUVScale);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
Ogre::String GzTerrainMatGen::SM2Profile::ShaderHelperGLSL::GetChannel(
|
|
Ogre::uint _idx)
|
|
{
|
|
Ogre::uint rem = _idx % 4;
|
|
switch (rem)
|
|
{
|
|
case 0:
|
|
default:
|
|
return "x";
|
|
case 1:
|
|
return "y";
|
|
case 2:
|
|
return "z";
|
|
case 3:
|
|
return "w";
|
|
};
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////
|
|
// CG Shader helper
|
|
/////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////
|
|
|
|
/////////////////////////////////////////////////
|
|
Ogre::HighLevelGpuProgramPtr
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperCg::generateVertexProgram(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt)
|
|
{
|
|
Ogre::HighLevelGpuProgramPtr ret =
|
|
this->createVertexProgram(_prof, _terrain, _tt);
|
|
|
|
Ogre::StringStream sourceStr;
|
|
this->generateVertexProgramSource(_prof, _terrain, _tt, sourceStr);
|
|
|
|
ret->setSource(sourceStr.str());
|
|
ret->load();
|
|
this->defaultVpParams(_prof, _terrain, _tt, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperCg::defaultVpParams(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, const Ogre::HighLevelGpuProgramPtr &_prog)
|
|
{
|
|
Ogre::GpuProgramParametersSharedPtr params = _prog->getDefaultParameters();
|
|
params->setIgnoreMissingParams(true);
|
|
|
|
params->setNamedAutoConstant("worldMatrix",
|
|
Ogre::GpuProgramParameters::ACT_WORLD_MATRIX);
|
|
|
|
params->setNamedAutoConstant("viewProjMatrix",
|
|
Ogre::GpuProgramParameters::ACT_VIEWPROJ_MATRIX);
|
|
|
|
params->setNamedAutoConstant("lodMorph",
|
|
Ogre::GpuProgramParameters::ACT_CUSTOM,
|
|
Ogre::Terrain::LOD_MORPH_CUSTOM_PARAM);
|
|
|
|
params->setNamedAutoConstant("fogParams",
|
|
Ogre::GpuProgramParameters::ACT_FOG_PARAMS);
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
unsigned int numTextures = 1;
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
}
|
|
for (unsigned int i = 0; i < numTextures; ++i)
|
|
{
|
|
params->setNamedAutoConstant("texViewProjMatrix" +
|
|
Ogre::StringConverter::toString(i),
|
|
Ogre::GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i);
|
|
|
|
// Don't add depth range params
|
|
// if (prof->getReceiveDynamicShadowsDepth())
|
|
// {
|
|
// params->setNamedAutoConstant("depthRange" +
|
|
// Ogre::StringConverter::toString(i),
|
|
// Ogre::GpuProgramParameters::ACT_SHADOW_SCENE_DEPTH_RANGE, i);
|
|
// }
|
|
}
|
|
}
|
|
|
|
#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8
|
|
if (_terrain->_getUseVertexCompression() && _tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
Ogre::Matrix4 posIndexToObjectSpace;
|
|
_terrain->getPointTransform(&posIndexToObjectSpace);
|
|
params->setNamedConstant("posIndexToObjectSpace", posIndexToObjectSpace);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperCg::generateVpDynamicShadows(
|
|
const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/,
|
|
TechniqueType /*_tt*/, Ogre::StringStream &_outStream)
|
|
{
|
|
unsigned int numTextures = 1;
|
|
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
}
|
|
|
|
// Calculate the position of vertex in light space
|
|
for (unsigned int i = 0; i < numTextures; ++i)
|
|
{
|
|
_outStream << " oLightSpacePos" << i << " = mul(texViewProjMatrix"
|
|
<< i << ", worldPos);\n";
|
|
|
|
// Don't linearize depth range: RTSS PSSM implementation uses
|
|
// view-space depth
|
|
// if (prof->getReceiveDynamicShadowsDepth())
|
|
// {
|
|
// // make linear
|
|
// outStream << "oLightSpacePos" << i << ".z = (oLightSpacePos" << i
|
|
// << ".z - depthRange" << i << ".x) * depthRange" << i
|
|
// << ".w;\n";
|
|
// }
|
|
}
|
|
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
_outStream << " // pass cam depth\n oUVMisc.z = oPos.z;\n";
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
unsigned int GzTerrainMatGen::SM2Profile::ShaderHelperCg::
|
|
generateVpDynamicShadowsParams(unsigned int _texCoord, const SM2Profile *_prof,
|
|
const Ogre::Terrain * /*_terrain*/, TechniqueType /*_tt*/,
|
|
Ogre::StringStream &_outStream)
|
|
{
|
|
// out semantics & params
|
|
unsigned int numTextures = 1;
|
|
|
|
if (_prof->getReceiveDynamicShadowsPSSM())
|
|
{
|
|
numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount();
|
|
}
|
|
|
|
for (unsigned int i = 0; i < numTextures; ++i)
|
|
{
|
|
_outStream << ", out float4 oLightSpacePos" << i
|
|
<< " : TEXCOORD" << _texCoord++ << "\n"
|
|
<< ", uniform float4x4 texViewProjMatrix" << i << "\n";
|
|
|
|
// Don't add depth range params
|
|
// if (prof->getReceiveDynamicShadowsDepth())
|
|
// {
|
|
// _outStream << ", uniform float4 depthRange" << i
|
|
// << " // x = min, y = max, z = range, w = 1/range\n";
|
|
// }
|
|
}
|
|
|
|
return _texCoord;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
// This method is identical to
|
|
// TerrainMaterialGeneratorA::SM2Profile::ShaderHelperCg::generateVpHeader()
|
|
// but is needed because generateVpDynamicShadowsParams() is not declared
|
|
// virtual.
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperCg::generateVpHeader(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, Ogre::StringStream &_outStream)
|
|
{
|
|
_outStream << "void main_vp(\n";
|
|
|
|
bool compression = false;
|
|
|
|
#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8
|
|
compression = _terrain->_getUseVertexCompression() &&
|
|
_tt != RENDER_COMPOSITE_MAP;
|
|
#endif
|
|
|
|
if (compression)
|
|
{
|
|
_outStream << "float2 posIndex : POSITION,\nfloat height : TEXCOORD0,\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream << "float4 pos : POSITION,\nfloat2 uv : TEXCOORD0,\n";
|
|
}
|
|
|
|
if (_tt != RENDER_COMPOSITE_MAP)
|
|
_outStream << "float2 delta : TEXCOORD1,\n";
|
|
|
|
_outStream <<
|
|
"uniform float4x4 worldMatrix,\n"
|
|
"uniform float4x4 viewProjMatrix,\n"
|
|
"uniform float2 lodMorph,\n";
|
|
|
|
if (compression)
|
|
{
|
|
_outStream <<
|
|
"uniform float4x4 posIndexToObjectSpace,\n"
|
|
"uniform float baseUVScale,\n";
|
|
}
|
|
|
|
// uv multipliers
|
|
Ogre::uint maxLayers = _prof->getMaxLayers(_terrain);
|
|
Ogre::uint numLayers = std::min(maxLayers,
|
|
static_cast<unsigned int>(_terrain->getLayerCount()));
|
|
|
|
unsigned int numUVMultipliers = (numLayers / 4);
|
|
|
|
if (numLayers % 4)
|
|
++numUVMultipliers;
|
|
|
|
for (unsigned int i = 0; i < numUVMultipliers; ++i)
|
|
_outStream << "uniform float4 uvMul" << i << ",\n";
|
|
|
|
_outStream <<
|
|
"out float4 oPos : POSITION,\n"
|
|
"out float4 oPosObj : TEXCOORD0\n";
|
|
|
|
unsigned int texCoordSet = 1;
|
|
_outStream << ", out float4 oUVMisc : TEXCOORD" << texCoordSet++
|
|
<< " // xy = uv, z = camDepth\n";
|
|
|
|
// layer UV's premultiplied, packed as xy/zw
|
|
unsigned int numUVSets = numLayers / 2;
|
|
|
|
if (numLayers % 2)
|
|
++numUVSets;
|
|
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
for (unsigned int i = 0; i < numUVSets; ++i)
|
|
{
|
|
_outStream << ", out float4 oUV" << i
|
|
<< " : TEXCOORD" << texCoordSet++ << "\n";
|
|
}
|
|
}
|
|
|
|
if (_prof->getParent()->getDebugLevel() && _tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
_outStream << ", out float2 lodInfo : TEXCOORD" << texCoordSet++ << "\n";
|
|
}
|
|
|
|
bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE &&
|
|
_tt != RENDER_COMPOSITE_MAP;
|
|
|
|
if (fog)
|
|
{
|
|
_outStream <<
|
|
", uniform float4 fogParams\n"
|
|
", out float fogVal : COLOR\n";
|
|
}
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
{
|
|
texCoordSet = generateVpDynamicShadowsParams(texCoordSet, _prof,
|
|
_terrain, _tt, _outStream);
|
|
}
|
|
|
|
// check we haven't exceeded texture coordinates
|
|
if (texCoordSet > 8)
|
|
{
|
|
OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS,
|
|
"Requested options require too many texture coordinate sets! "
|
|
"Try reducing the number of layers.",
|
|
__FUNCTION__);
|
|
}
|
|
|
|
_outStream <<
|
|
")\n"
|
|
"{\n";
|
|
|
|
if (compression)
|
|
{
|
|
_outStream << " float4 pos;\n"
|
|
<< " pos = mul(posIndexToObjectSpace, float4(posIndex, height, 1));\n"
|
|
<< " float2 uv = float2(posIndex.x * baseUVScale, 1.0 - "
|
|
<< "(posIndex.y * baseUVScale));\n";
|
|
}
|
|
|
|
_outStream <<
|
|
" float4 worldPos = mul(worldMatrix, pos);\n"
|
|
" oPosObj = pos;\n";
|
|
|
|
if (_tt != RENDER_COMPOSITE_MAP)
|
|
{
|
|
// determine whether to apply the LOD morph to this vertex
|
|
// we store the deltas against all vertices so we only want to apply
|
|
// the morph to the ones which would disappear. The target LOD which is
|
|
// being morphed to is stored in lodMorph.y, and the LOD at which
|
|
// the vertex should be morphed is stored in uv.w. If we subtract
|
|
// the former from the latter, and arrange to only morph if the
|
|
// result is negative (it will only be -1 in fact, since after that
|
|
// the vertex will never be indexed), we will achieve our aim.
|
|
// sign(vertexLOD - targetLOD) == -1 is to morph
|
|
_outStream <<
|
|
" float toMorph = -min(0, sign(delta.y - lodMorph.y));\n";
|
|
|
|
// this will either be 1 (morph) or 0 (don't morph)
|
|
if (_prof->getParent()->getDebugLevel())
|
|
{
|
|
// x == LOD level (-1 since value is target level, we want to
|
|
// display actual)
|
|
_outStream << "lodInfo.x = (lodMorph.y - 1) / "
|
|
<< _terrain->getNumLodLevels() << ";\n";
|
|
|
|
// y == LOD morph
|
|
_outStream << "lodInfo.y = toMorph * lodMorph.x;\n";
|
|
}
|
|
|
|
// morph
|
|
switch (_terrain->getAlignment())
|
|
{
|
|
case Ogre::Terrain::ALIGN_X_Y:
|
|
_outStream << " worldPos.z += delta.x * toMorph * lodMorph.x;\n";
|
|
break;
|
|
case Ogre::Terrain::ALIGN_X_Z:
|
|
_outStream << " worldPos.y += delta.x * toMorph * lodMorph.x;\n";
|
|
break;
|
|
case Ogre::Terrain::ALIGN_Y_Z:
|
|
_outStream << " worldPos.x += delta.x * toMorph * lodMorph.x;\n";
|
|
break;
|
|
default:
|
|
gzerr << "Invalid alignment\n";
|
|
};
|
|
}
|
|
|
|
// generate UVs
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
for (unsigned int i = 0; i < numUVSets; ++i)
|
|
{
|
|
unsigned int layer = i * 2;
|
|
unsigned int uvMulIdx = layer / 4;
|
|
|
|
_outStream << " oUV" << i << ".xy = " << " uv.xy * uvMul"
|
|
<< uvMulIdx << "." << getChannel(layer) << ";\n";
|
|
_outStream << " oUV" << i << ".zw = " << " uv.xy * uvMul"
|
|
<< uvMulIdx << "." << getChannel(layer+1) << ";\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
// This method is identical to
|
|
// TerrainMaterialGeneratorA::SM2Profile::ShaderHelperCg::generateVpFooter()
|
|
// but is needed because generateVpDynamicShadows() is not declared virtual.
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperCg::generateVpFooter(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain,
|
|
TechniqueType _tt, Ogre::StringStream &_outStream)
|
|
{
|
|
_outStream << " oPos = mul(viewProjMatrix, worldPos);\n"
|
|
<< " oUVMisc.xy = uv.xy;\n";
|
|
|
|
bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE &&
|
|
_tt != RENDER_COMPOSITE_MAP;
|
|
if (fog)
|
|
{
|
|
if (_terrain->getSceneManager()->getFogMode() == Ogre::FOG_LINEAR)
|
|
{
|
|
_outStream <<
|
|
" fogVal = saturate((oPos.z - fogParams.y) * fogParams.w);\n";
|
|
}
|
|
else
|
|
{
|
|
_outStream <<
|
|
" fogVal = 1 - saturate(1 / (exp(oPos.z * fogParams.x)));\n";
|
|
}
|
|
}
|
|
|
|
if (_prof->isShadowingEnabled(_tt, _terrain))
|
|
this->generateVpDynamicShadows(_prof, _terrain, _tt, _outStream);
|
|
|
|
_outStream << "}\n";
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void GzTerrainMatGen::SM2Profile::ShaderHelperCg::
|
|
generateVertexProgramSource(const SM2Profile *_prof,
|
|
const Ogre::Terrain* _terrain, TechniqueType _tt,
|
|
Ogre::StringStream &_outStream)
|
|
{
|
|
this->generateVpHeader(_prof, _terrain, _tt, _outStream);
|
|
|
|
if (_tt != LOW_LOD)
|
|
{
|
|
unsigned int maxLayers = _prof->getMaxLayers(_terrain);
|
|
unsigned int numLayers = std::min(maxLayers,
|
|
static_cast<unsigned int>(_terrain->getLayerCount()));
|
|
|
|
for (unsigned int i = 0; i < numLayers; ++i)
|
|
this->generateVpLayer(_prof, _terrain, _tt, i, _outStream);
|
|
}
|
|
|
|
this->generateVpFooter(_prof, _terrain, _tt, _outStream);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
Ogre::HighLevelGpuProgramPtr
|
|
GzTerrainMatGen::SM2Profile::ShaderHelperCg::generateFragmentProgram(
|
|
const SM2Profile *_prof, const Ogre::Terrain *_terrain, TechniqueType _tt)
|
|
{
|
|
Ogre::HighLevelGpuProgramPtr ret = this->createFragmentProgram(_prof,
|
|
_terrain, _tt);
|
|
|
|
Ogre::StringStream sourceStr;
|
|
|
|
this->generateFragmentProgramSource(_prof, _terrain, _tt, sourceStr);
|
|
|
|
ret->setSource(sourceStr.str());
|
|
|
|
ret->load();
|
|
|
|
this->defaultFpParams(_prof, _terrain, _tt, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////
|
|
// TerrainMaterial
|
|
/////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////
|
|
TerrainMaterial::TerrainMaterial(const std::string &_materialname)
|
|
{
|
|
this->materialName = _materialname;
|
|
this->mProfiles.push_back(OGRE_NEW Profile(this, "OgreMaterial",
|
|
"Profile for rendering Ogre standard material"));
|
|
this->setActiveProfile("OgreMaterial");
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void TerrainMaterial::setMaterialByName(const std::string &_materialname)
|
|
{
|
|
this->materialName = _materialname;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void TerrainMaterial::setGridSize(const unsigned int _size)
|
|
{
|
|
if (_size == 0)
|
|
{
|
|
gzerr << "Unable to set a grid size of zero" << std::endl;
|
|
return;
|
|
}
|
|
|
|
this->gridSize = _size;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator *_parent,
|
|
const Ogre::String &_name, const Ogre::String &_desc)
|
|
: Ogre::TerrainMaterialGenerator::Profile(_parent, _name, _desc)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
TerrainMaterial::Profile::~Profile()
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
bool TerrainMaterial::Profile::isVertexCompressionSupported() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
Ogre::MaterialPtr TerrainMaterial::Profile::generate(
|
|
const Ogre::Terrain *_terrain)
|
|
{
|
|
const Ogre::String& matName = _terrain->getMaterialName();
|
|
|
|
Ogre::MaterialPtr mat =
|
|
Ogre::MaterialManager::getSingleton().getByName(matName);
|
|
if (!mat.isNull())
|
|
Ogre::MaterialManager::getSingleton().remove(matName);
|
|
|
|
TerrainMaterial *parent =
|
|
dynamic_cast<TerrainMaterial *>(getParent());
|
|
|
|
// Set Ogre material
|
|
mat = Ogre::MaterialManager::getSingleton().getByName(parent->materialName);
|
|
|
|
// clone the material
|
|
mat = mat->clone(matName);
|
|
if (!mat->isLoaded())
|
|
mat->load();
|
|
|
|
// size of grid in one direction
|
|
unsigned int gridWidth =
|
|
static_cast<unsigned int>(std::sqrt(parent->gridSize));
|
|
// factor to be applied to uv transformation: scale and translation
|
|
double factor = 1.0 / gridWidth;
|
|
// static counter to keep track which terrain slot we are currently in
|
|
static int gridCount = 0;
|
|
|
|
for (unsigned int i = 0; i < mat->getNumTechniques(); ++i)
|
|
{
|
|
Ogre::Technique *tech = mat->getTechnique(i);
|
|
for (unsigned int j = 0; j < tech->getNumPasses(); ++j)
|
|
{
|
|
Ogre::Pass *pass = tech->getPass(j);
|
|
|
|
// check if there is a fragment shader
|
|
if (!pass->hasFragmentProgram())
|
|
continue;
|
|
|
|
Ogre::GpuProgramParametersSharedPtr params =
|
|
pass->getFragmentProgramParameters();
|
|
if (params.isNull())
|
|
continue;
|
|
|
|
// set up shadow split points in a way that is consistent with the
|
|
// default ogre terrain material generator
|
|
Ogre::PSSMShadowCameraSetup* pssm =
|
|
RTShaderSystem::Instance()->GetPSSMShadowCameraSetup();
|
|
unsigned int numTextures =
|
|
static_cast<unsigned int>(pssm->getSplitCount());
|
|
Ogre::Vector4 splitPoints;
|
|
const Ogre::PSSMShadowCameraSetup::SplitPointList& splitPointList =
|
|
pssm->getSplitPoints();
|
|
// populate from split point 1 not 0, and include shadowFarDistance
|
|
for (unsigned int t = 0u; t < numTextures; ++t)
|
|
splitPoints[t] = splitPointList[t+1];
|
|
params->setNamedConstant("pssmSplitPoints", splitPoints);
|
|
|
|
// set up uv transform
|
|
double xTrans = static_cast<int>(gridCount / gridWidth) * factor;
|
|
double yTrans = (gridWidth - 1 - (gridCount % gridWidth)) * factor;
|
|
// explicitly set all matrix elements to avoid uninitialized values
|
|
Ogre::Matrix4 uvTransform(factor, 0.0, 0.0, xTrans,
|
|
0.0, factor, 0.0, yTrans,
|
|
0.0, 0.0, 1.0, 0.0,
|
|
0.0, 0.0, 0.0, 1.0);
|
|
params->setNamedConstant("uvTransform", uvTransform);
|
|
}
|
|
}
|
|
gridCount++;
|
|
|
|
// Get default pass
|
|
Ogre::Pass *p = mat->getTechnique(0)->getPass(0);
|
|
|
|
// Add terrain's global normalmap to renderpass so the
|
|
// fragment program can find it.
|
|
Ogre::TextureUnitState *tu = p->createTextureUnitState(matName+"/nm");
|
|
|
|
Ogre::TexturePtr nmtx = _terrain->getTerrainNormalMap();
|
|
tu->_setTexturePtr(nmtx);
|
|
|
|
return mat;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(
|
|
const Ogre::Terrain *_terrain)
|
|
{
|
|
return _terrain->_getCompositeMapMaterial();
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void TerrainMaterial::Profile::setLightmapEnabled(bool /*_enabled*/)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
Ogre::uint8 TerrainMaterial::Profile::getMaxLayers(
|
|
const Ogre::Terrain */*_terrain*/) const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr &/*_mat*/,
|
|
const Ogre::Terrain */*_terrain*/)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void TerrainMaterial::Profile::updateParamsForCompositeMap(
|
|
const Ogre::MaterialPtr &/*_mat*/, const Ogre::Terrain */*_terrain*/)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void TerrainMaterial::Profile::requestOptions(Ogre::Terrain *_terrain)
|
|
{
|
|
_terrain->_setMorphRequired(true);
|
|
// enable global normal map
|
|
_terrain->_setNormalMapRequired(true);
|
|
_terrain->_setLightMapRequired(false);
|
|
_terrain->_setCompositeMapRequired(false);
|
|
}
|