pxmlw6n2f/Gazebo_Distributed_TCP/gazebo/rendering/Heightmap.cc

3440 lines
105 KiB
C++
Raw Normal View History

2019-03-28 10:57:49 +08:00
/*
* 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, &currentSamplerIdx,
_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);
}