Merge branch 'master' of https://github.com/zinnschlag/openmw.git into Factions2
Conflicts: apps/openmw/mwscript/docs/vmformat.txtactorid
commit
07ea63c10c
@ -0,0 +1,20 @@
|
|||||||
|
set(MWINIIMPORT
|
||||||
|
main.cpp
|
||||||
|
importer.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(MWINIIMPORT_HEADER
|
||||||
|
importer.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
|
||||||
|
|
||||||
|
add_executable(mwiniimport
|
||||||
|
${MWINIIMPORT}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(mwiniimport
|
||||||
|
${Boost_LIBRARIES}
|
||||||
|
components
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,184 @@
|
|||||||
|
#include "importer.hpp"
|
||||||
|
#include <boost/iostreams/device/file.hpp>
|
||||||
|
#include <boost/iostreams/stream.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
MwIniImporter::MwIniImporter() {
|
||||||
|
const char *map[][2] =
|
||||||
|
{
|
||||||
|
{ "fps", "General:Show FPS" },
|
||||||
|
{ 0, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
for(int i=0; map[i][0]; i++) {
|
||||||
|
mMergeMap.insert(std::make_pair<std::string, std::string>(map[i][0], map[i][1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MwIniImporter::setVerbose(bool verbose) {
|
||||||
|
mVerbose = verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MwIniImporter::numberToString(int n) {
|
||||||
|
std::stringstream str;
|
||||||
|
str << n;
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) {
|
||||||
|
std::cout << "load ini file: " << filename << std::endl;
|
||||||
|
|
||||||
|
std::string section("");
|
||||||
|
MwIniImporter::multistrmap map;
|
||||||
|
boost::iostreams::stream<boost::iostreams::file_source>file(filename.c_str());
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
|
||||||
|
if(line[0] == '[') {
|
||||||
|
if(line.length() > 2) {
|
||||||
|
section = line.substr(1, line.length()-3);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int comment_pos = line.find(";");
|
||||||
|
if(comment_pos > 0) {
|
||||||
|
line = line.substr(0,comment_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = line.find("=");
|
||||||
|
if(pos < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key(section + ":" + line.substr(0,pos));
|
||||||
|
std::string value(line.substr(pos+1));
|
||||||
|
|
||||||
|
multistrmap::iterator it;
|
||||||
|
if((it = map.find(key)) == map.end()) {
|
||||||
|
map.insert( std::make_pair<std::string, std::vector<std::string> > (key, std::vector<std::string>() ) );
|
||||||
|
}
|
||||||
|
map[key].push_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
MwIniImporter::multistrmap MwIniImporter::loadCfgFile(std::string filename) {
|
||||||
|
std::cout << "load cfg file: " << filename << std::endl;
|
||||||
|
|
||||||
|
MwIniImporter::multistrmap map;
|
||||||
|
boost::iostreams::stream<boost::iostreams::file_source>file(filename.c_str());
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
|
||||||
|
// we cant say comment by only looking at first char anymore
|
||||||
|
int comment_pos = line.find("#");
|
||||||
|
if(comment_pos > 0) {
|
||||||
|
line = line.substr(0,comment_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = line.find("=");
|
||||||
|
if(pos < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key(line.substr(0,pos));
|
||||||
|
std::string value(line.substr(pos+1));
|
||||||
|
|
||||||
|
multistrmap::iterator it;
|
||||||
|
if((it = map.find(key)) == map.end()) {
|
||||||
|
map.insert( std::make_pair<std::string, std::vector<std::string> > (key, std::vector<std::string>() ) );
|
||||||
|
}
|
||||||
|
map[key].push_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MwIniImporter::merge(multistrmap &cfg, multistrmap &ini) {
|
||||||
|
multistrmap::iterator cfgIt;
|
||||||
|
multistrmap::iterator iniIt;
|
||||||
|
for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); it++) {
|
||||||
|
if((iniIt = ini.find(it->second)) != ini.end()) {
|
||||||
|
cfg.erase(it->first);
|
||||||
|
if(!this->specialMerge(it->first, it->second, cfg, ini)) {
|
||||||
|
cfg.insert(std::make_pair<std::string, std::vector<std::string> >(it->first, iniIt->second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) {
|
||||||
|
std::vector<std::string> esmFiles;
|
||||||
|
std::vector<std::string> espFiles;
|
||||||
|
std::string baseGameFile("Game Files:GameFile");
|
||||||
|
std::string gameFile("");
|
||||||
|
|
||||||
|
multistrmap::iterator it = ini.begin();
|
||||||
|
for(int i=0; it != ini.end(); i++) {
|
||||||
|
gameFile = baseGameFile;
|
||||||
|
gameFile.append(this->numberToString(i));
|
||||||
|
|
||||||
|
it = ini.find(gameFile);
|
||||||
|
if(it == ini.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(std::vector<std::string>::iterator entry = it->second.begin(); entry!=it->second.end(); entry++) {
|
||||||
|
std::string filetype(entry->substr(entry->length()-4, 3));
|
||||||
|
std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower);
|
||||||
|
|
||||||
|
if(filetype.compare("esm") == 0) {
|
||||||
|
esmFiles.push_back(*entry);
|
||||||
|
}
|
||||||
|
else if(filetype.compare("esp") == 0) {
|
||||||
|
espFiles.push_back(*entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gameFile = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.erase("master");
|
||||||
|
cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("master", std::vector<std::string>() ) );
|
||||||
|
|
||||||
|
for(std::vector<std::string>::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) {
|
||||||
|
cfg["master"].push_back(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.erase("plugin");
|
||||||
|
cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("plugin", std::vector<std::string>() ) );
|
||||||
|
|
||||||
|
for(std::vector<std::string>::iterator it=espFiles.begin(); it!=espFiles.end(); it++) {
|
||||||
|
cfg["plugin"].push_back(*it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MwIniImporter::writeToFile(boost::iostreams::stream<boost::iostreams::file_sink> &out, multistrmap &cfg) {
|
||||||
|
|
||||||
|
for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); it++) {
|
||||||
|
for(std::vector<std::string>::iterator entry=it->second.begin(); entry != it->second.end(); entry++) {
|
||||||
|
out << (it->first) << "=" << (*entry) << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef MWINIIMPORTER_IMPORTER
|
||||||
|
#define MWINIIMPORTER_IMPORTER 1
|
||||||
|
|
||||||
|
#include <boost/iostreams/device/file.hpp>
|
||||||
|
#include <boost/iostreams/stream.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
class MwIniImporter {
|
||||||
|
public:
|
||||||
|
typedef std::map<std::string, std::string> strmap;
|
||||||
|
typedef std::map<std::string, std::vector<std::string> > multistrmap;
|
||||||
|
|
||||||
|
MwIniImporter();
|
||||||
|
void setVerbose(bool verbose);
|
||||||
|
multistrmap loadIniFile(std::string filename);
|
||||||
|
multistrmap loadCfgFile(std::string filename);
|
||||||
|
void merge(multistrmap &cfg, multistrmap &ini);
|
||||||
|
void importGameFiles(multistrmap &cfg, multistrmap &ini);
|
||||||
|
void writeToFile(boost::iostreams::stream<boost::iostreams::file_sink> &out, multistrmap &cfg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini);
|
||||||
|
std::string numberToString(int n);
|
||||||
|
bool mVerbose;
|
||||||
|
strmap mMergeMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,79 @@
|
|||||||
|
#include "importer.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <boost/program_options.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
namespace bpo = boost::program_options;
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
|
bpo::options_description desc("Syntax: mwiniimporter <options>\nAllowed options");
|
||||||
|
desc.add_options()
|
||||||
|
("help,h", "produce help message")
|
||||||
|
("verbose,v", "verbose output")
|
||||||
|
("ini,i", bpo::value<std::string>(), "morrowind.ini file")
|
||||||
|
("cfg,c", bpo::value<std::string>(), "openmw.cfg file")
|
||||||
|
("output,o", bpo::value<std::string>()->default_value(""), "openmw.cfg file")
|
||||||
|
("game-files,g", "import esm and esp files")
|
||||||
|
;
|
||||||
|
|
||||||
|
bpo::variables_map vm;
|
||||||
|
try {
|
||||||
|
bpo::store(boost::program_options::parse_command_line(argc, argv, desc), vm);
|
||||||
|
|
||||||
|
// parse help before calling notify because we dont want it to throw an error if help is set
|
||||||
|
if(vm.count("help")) {
|
||||||
|
std::cout << desc;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bpo::notify(vm);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(std::exception& e) {
|
||||||
|
std::cerr << "Error:" << e.what() << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
std::cerr << "Error" << std::endl;
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string iniFile = vm["ini"].as<std::string>();
|
||||||
|
std::string cfgFile = vm["cfg"].as<std::string>();
|
||||||
|
|
||||||
|
// if no output is given, write back to cfg file
|
||||||
|
std::string outputFile(vm["output"].as<std::string>());
|
||||||
|
if(vm["output"].defaulted()) {
|
||||||
|
outputFile = vm["cfg"].as<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!boost::filesystem::exists(iniFile)) {
|
||||||
|
std::cerr << "ini file does not exist" << std::endl;
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
if(!boost::filesystem::exists(cfgFile)) {
|
||||||
|
std::cerr << "cfg file does not exist" << std::endl;
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
MwIniImporter importer;
|
||||||
|
importer.setVerbose(vm.count("verbose"));
|
||||||
|
boost::iostreams::stream<boost::iostreams::file_sink> file(outputFile);
|
||||||
|
|
||||||
|
MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile);
|
||||||
|
MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile);
|
||||||
|
|
||||||
|
importer.merge(cfg, ini);
|
||||||
|
|
||||||
|
if(vm.count("game-files")) {
|
||||||
|
importer.importGameFiles(cfg, ini);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "write to: " << outputFile << std::endl;
|
||||||
|
importer.writeToFile(file, cfg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,266 @@
|
|||||||
|
#include "occlusionquery.hpp"
|
||||||
|
|
||||||
|
#include <OgreRenderSystem.h>
|
||||||
|
#include <OgreRoot.h>
|
||||||
|
#include <OgreBillboardSet.h>
|
||||||
|
#include <OgreHardwareOcclusionQuery.h>
|
||||||
|
#include <OgreEntity.h>
|
||||||
|
|
||||||
|
using namespace MWRender;
|
||||||
|
using namespace Ogre;
|
||||||
|
|
||||||
|
OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) :
|
||||||
|
mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0),
|
||||||
|
mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false),
|
||||||
|
mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false),
|
||||||
|
mBBNode(0)
|
||||||
|
{
|
||||||
|
mRendering = renderer;
|
||||||
|
mSunNode = sunNode;
|
||||||
|
|
||||||
|
try {
|
||||||
|
RenderSystem* renderSystem = Root::getSingleton().getRenderSystem();
|
||||||
|
|
||||||
|
mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery();
|
||||||
|
mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery();
|
||||||
|
mSingleObjectQuery = renderSystem->createHardwareOcclusionQuery();
|
||||||
|
|
||||||
|
mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0) && (mSingleObjectQuery != 0);
|
||||||
|
}
|
||||||
|
catch (Ogre::Exception e)
|
||||||
|
{
|
||||||
|
mSupported = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mSupported)
|
||||||
|
{
|
||||||
|
std::cout << "Hardware occlusion queries not supported." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This means that everything up to RENDER_QUEUE_MAIN can occlude the objects that are tested
|
||||||
|
const int queue = RENDER_QUEUE_MAIN+1;
|
||||||
|
|
||||||
|
MaterialPtr matBase = MaterialManager::getSingleton().getByName("BaseWhiteNoLighting");
|
||||||
|
MaterialPtr matQueryArea = matBase->clone("QueryTotalPixels");
|
||||||
|
matQueryArea->setDepthWriteEnabled(false);
|
||||||
|
matQueryArea->setColourWriteEnabled(false);
|
||||||
|
matQueryArea->setDepthCheckEnabled(false); // Not occluded by objects
|
||||||
|
MaterialPtr matQueryVisible = matBase->clone("QueryVisiblePixels");
|
||||||
|
matQueryVisible->setDepthWriteEnabled(false);
|
||||||
|
matQueryVisible->setColourWriteEnabled(false); // Uncomment this to visualize the occlusion query
|
||||||
|
matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects
|
||||||
|
matQueryVisible->setCullingMode(CULL_NONE);
|
||||||
|
matQueryVisible->setManualCullingMode(MANUAL_CULL_NONE);
|
||||||
|
|
||||||
|
if (sunNode)
|
||||||
|
mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode();
|
||||||
|
|
||||||
|
mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode();
|
||||||
|
mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode();
|
||||||
|
|
||||||
|
mBBQueryTotal = mRendering->getScene()->createBillboardSet(1);
|
||||||
|
mBBQueryTotal->setDefaultDimensions(150, 150);
|
||||||
|
mBBQueryTotal->createBillboard(Vector3::ZERO);
|
||||||
|
mBBQueryTotal->setMaterialName("QueryTotalPixels");
|
||||||
|
mBBQueryTotal->setRenderQueueGroup(queue+1);
|
||||||
|
mBBNodeReal->attachObject(mBBQueryTotal);
|
||||||
|
|
||||||
|
mBBQueryVisible = mRendering->getScene()->createBillboardSet(1);
|
||||||
|
mBBQueryVisible->setDefaultDimensions(150, 150);
|
||||||
|
mBBQueryVisible->createBillboard(Vector3::ZERO);
|
||||||
|
mBBQueryVisible->setMaterialName("QueryVisiblePixels");
|
||||||
|
mBBQueryVisible->setRenderQueueGroup(queue+1);
|
||||||
|
mBBNodeReal->attachObject(mBBQueryVisible);
|
||||||
|
|
||||||
|
mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1);
|
||||||
|
/// \todo ideally this should occupy exactly 1 pixel on the screen
|
||||||
|
mBBQuerySingleObject->setDefaultDimensions(0.003, 0.003);
|
||||||
|
mBBQuerySingleObject->createBillboard(Vector3::ZERO);
|
||||||
|
mBBQuerySingleObject->setMaterialName("QueryVisiblePixels");
|
||||||
|
mBBQuerySingleObject->setRenderQueueGroup(queue);
|
||||||
|
mObjectNode->attachObject(mBBQuerySingleObject);
|
||||||
|
|
||||||
|
mRendering->getScene()->addRenderObjectListener(this);
|
||||||
|
mRendering->getScene()->addRenderQueueListener(this);
|
||||||
|
mDoQuery = true;
|
||||||
|
mDoQuery2 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OcclusionQuery::~OcclusionQuery()
|
||||||
|
{
|
||||||
|
RenderSystem* renderSystem = Root::getSingleton().getRenderSystem();
|
||||||
|
if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery);
|
||||||
|
if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery);
|
||||||
|
if (mSingleObjectQuery) renderSystem->destroyHardwareOcclusionQuery(mSingleObjectQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OcclusionQuery::supported()
|
||||||
|
{
|
||||||
|
return mSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source,
|
||||||
|
const LightList* pLightList, bool suppressRenderStateChanges)
|
||||||
|
{
|
||||||
|
// The following code activates and deactivates the occlusion queries
|
||||||
|
// so that the queries only include the rendering of their intended targets
|
||||||
|
|
||||||
|
// Close the last occlusion query
|
||||||
|
// Each occlusion query should only last a single rendering
|
||||||
|
if (mActiveQuery != NULL)
|
||||||
|
{
|
||||||
|
mActiveQuery->endOcclusionQuery();
|
||||||
|
mActiveQuery = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a new occlusion query
|
||||||
|
if (mDoQuery == true)
|
||||||
|
{
|
||||||
|
if (rend == mBBQueryTotal)
|
||||||
|
{
|
||||||
|
mActiveQuery = mSunTotalAreaQuery;
|
||||||
|
mWasVisible = true;
|
||||||
|
}
|
||||||
|
else if (rend == mBBQueryVisible)
|
||||||
|
{
|
||||||
|
mActiveQuery = mSunVisibleAreaQuery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mDoQuery == true && rend == mBBQuerySingleObject)
|
||||||
|
{
|
||||||
|
mQuerySingleObjectStarted = true;
|
||||||
|
mQuerySingleObjectRequested = false;
|
||||||
|
mActiveQuery = mSingleObjectQuery;
|
||||||
|
mObjectWasVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mActiveQuery != NULL)
|
||||||
|
mActiveQuery->beginOcclusionQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation)
|
||||||
|
{
|
||||||
|
if (mActiveQuery != NULL)
|
||||||
|
{
|
||||||
|
mActiveQuery->endOcclusionQuery();
|
||||||
|
mActiveQuery = NULL;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa
|
||||||
|
* this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called
|
||||||
|
* this can happen for example if the object that is tested is outside of the view frustum
|
||||||
|
* to prevent this, check if the queries have been performed after everything has been rendered and if not, start them manually
|
||||||
|
*/
|
||||||
|
if (queueGroupId == RENDER_QUEUE_SKIES_LATE)
|
||||||
|
{
|
||||||
|
if (mWasVisible == false && mDoQuery)
|
||||||
|
{
|
||||||
|
mSunTotalAreaQuery->beginOcclusionQuery();
|
||||||
|
mSunTotalAreaQuery->endOcclusionQuery();
|
||||||
|
mSunVisibleAreaQuery->beginOcclusionQuery();
|
||||||
|
mSunVisibleAreaQuery->endOcclusionQuery();
|
||||||
|
}
|
||||||
|
if (mObjectWasVisible == false && mDoQuery)
|
||||||
|
{
|
||||||
|
mSingleObjectQuery->beginOcclusionQuery();
|
||||||
|
mSingleObjectQuery->endOcclusionQuery();
|
||||||
|
mQuerySingleObjectStarted = true;
|
||||||
|
mQuerySingleObjectRequested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OcclusionQuery::update(float duration)
|
||||||
|
{
|
||||||
|
if (!mSupported) return;
|
||||||
|
|
||||||
|
mWasVisible = false;
|
||||||
|
mObjectWasVisible = false;
|
||||||
|
|
||||||
|
// Adjust the position of the sun billboards according to camera viewing distance
|
||||||
|
// we need to do this to make sure that _everything_ can occlude the sun
|
||||||
|
float dist = mRendering->getCamera()->getFarClipDistance();
|
||||||
|
if (dist==0) dist = 10000000;
|
||||||
|
dist -= 1000; // bias
|
||||||
|
dist /= 1000.f;
|
||||||
|
if (mBBNode)
|
||||||
|
{
|
||||||
|
mBBNode->setPosition(mSunNode->getPosition() * dist);
|
||||||
|
mBBNode->setScale(dist, dist, dist);
|
||||||
|
mBBNodeReal->setPosition(mBBNode->_getDerivedPosition());
|
||||||
|
mBBNodeReal->setScale(mBBNode->getScale());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop occlusion queries until we get their information
|
||||||
|
// (may not happen on the same frame they are requested in)
|
||||||
|
mDoQuery = false;
|
||||||
|
mDoQuery2 = false;
|
||||||
|
|
||||||
|
if (!mSunTotalAreaQuery->isStillOutstanding()
|
||||||
|
&& !mSunVisibleAreaQuery->isStillOutstanding()
|
||||||
|
&& !mSingleObjectQuery->isStillOutstanding())
|
||||||
|
{
|
||||||
|
unsigned int totalPixels;
|
||||||
|
unsigned int visiblePixels;
|
||||||
|
|
||||||
|
mSunTotalAreaQuery->pullOcclusionQuery(&totalPixels);
|
||||||
|
mSunVisibleAreaQuery->pullOcclusionQuery(&visiblePixels);
|
||||||
|
|
||||||
|
if (totalPixels == 0)
|
||||||
|
{
|
||||||
|
// probably outside of the view frustum
|
||||||
|
mSunVisibility = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSunVisibility = float(visiblePixels) / float(totalPixels);
|
||||||
|
if (mSunVisibility > 1) mSunVisibility = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int result;
|
||||||
|
|
||||||
|
mSingleObjectQuery->pullOcclusionQuery(&result);
|
||||||
|
|
||||||
|
mTestResult = (result != 0);
|
||||||
|
|
||||||
|
mQuerySingleObjectStarted = false;
|
||||||
|
mQuerySingleObjectRequested = false;
|
||||||
|
|
||||||
|
mDoQuery = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object)
|
||||||
|
{
|
||||||
|
assert( !occlusionTestPending()
|
||||||
|
&& "Occlusion test still pending");
|
||||||
|
|
||||||
|
mBBQuerySingleObject->setVisible(true);
|
||||||
|
|
||||||
|
mObjectNode->setPosition(position);
|
||||||
|
// scale proportional to camera distance, in order to always give the billboard the same size in screen-space
|
||||||
|
mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() );
|
||||||
|
|
||||||
|
mQuerySingleObjectRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OcclusionQuery::occlusionTestPending()
|
||||||
|
{
|
||||||
|
return (mQuerySingleObjectRequested || mQuerySingleObjectStarted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OcclusionQuery::setSunNode(Ogre::SceneNode* node)
|
||||||
|
{
|
||||||
|
mSunNode = node;
|
||||||
|
if (!mBBNode)
|
||||||
|
mBBNode = node->getParentSceneNode()->createChildSceneNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OcclusionQuery::getTestResult()
|
||||||
|
{
|
||||||
|
assert( !occlusionTestPending()
|
||||||
|
&& "Occlusion test still pending");
|
||||||
|
|
||||||
|
return mTestResult;
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
#ifndef _GAME_OCCLUSION_QUERY_H
|
||||||
|
#define _GAME_OCCLUSION_QUERY_H
|
||||||
|
|
||||||
|
#include <OgreRenderObjectListener.h>
|
||||||
|
#include <OgreRenderQueueListener.h>
|
||||||
|
|
||||||
|
namespace Ogre
|
||||||
|
{
|
||||||
|
class HardwareOcclusionQuery;
|
||||||
|
class Entity;
|
||||||
|
class SceneNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <openengine/ogre/renderer.hpp>
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
///
|
||||||
|
/// \brief Implements hardware occlusion queries on the GPU
|
||||||
|
///
|
||||||
|
class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode);
|
||||||
|
~OcclusionQuery();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if occlusion queries are supported on the user's hardware
|
||||||
|
*/
|
||||||
|
bool supported();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* per-frame update
|
||||||
|
*/
|
||||||
|
void update(float duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request occlusion test for a billboard at the given position, omitting an entity
|
||||||
|
* @param position of the billboard in ogre coordinates
|
||||||
|
* @param object to exclude from the occluders
|
||||||
|
*/
|
||||||
|
void occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if a request is still outstanding
|
||||||
|
*/
|
||||||
|
bool occlusionTestPending();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the object tested in the last request was occluded
|
||||||
|
*/
|
||||||
|
bool getTestResult();
|
||||||
|
|
||||||
|
float getSunVisibility() const {return mSunVisibility;};
|
||||||
|
|
||||||
|
void setSunNode(Ogre::SceneNode* node);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery;
|
||||||
|
Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery;
|
||||||
|
Ogre::HardwareOcclusionQuery* mSingleObjectQuery;
|
||||||
|
Ogre::HardwareOcclusionQuery* mActiveQuery;
|
||||||
|
|
||||||
|
Ogre::BillboardSet* mBBQueryVisible;
|
||||||
|
Ogre::BillboardSet* mBBQueryTotal;
|
||||||
|
Ogre::BillboardSet* mBBQuerySingleObject;
|
||||||
|
|
||||||
|
Ogre::SceneNode* mSunNode;
|
||||||
|
Ogre::SceneNode* mBBNode;
|
||||||
|
Ogre::SceneNode* mBBNodeReal;
|
||||||
|
float mSunVisibility;
|
||||||
|
|
||||||
|
Ogre::SceneNode* mObjectNode;
|
||||||
|
|
||||||
|
bool mWasVisible;
|
||||||
|
bool mObjectWasVisible;
|
||||||
|
|
||||||
|
bool mTestResult;
|
||||||
|
|
||||||
|
bool mSupported;
|
||||||
|
bool mDoQuery;
|
||||||
|
bool mDoQuery2;
|
||||||
|
|
||||||
|
bool mQuerySingleObjectRequested;
|
||||||
|
bool mQuerySingleObjectStarted;
|
||||||
|
|
||||||
|
OEngine::Render::OgreRenderer* mRendering;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source,
|
||||||
|
const Ogre::LightList* pLightList, bool suppressRenderStateChanges);
|
||||||
|
|
||||||
|
virtual void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,510 @@
|
|||||||
|
#include <OgreTerrain.h>
|
||||||
|
#include <OgreTerrainGroup.h>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
|
#include "../mwworld/world.hpp"
|
||||||
|
|
||||||
|
#include "terrainmaterial.hpp"
|
||||||
|
#include "terrain.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace Ogre;
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TerrainManager::TerrainManager(Ogre::SceneManager* mgr, const MWWorld::Environment& evn) :
|
||||||
|
mEnvironment(evn), mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Z, mLandSize, mWorldSize))
|
||||||
|
{
|
||||||
|
|
||||||
|
TerrainMaterialGeneratorPtr matGen;
|
||||||
|
TerrainMaterialGeneratorB* matGenP = new TerrainMaterialGeneratorB();
|
||||||
|
matGen.bind(matGenP);
|
||||||
|
mTerrainGlobals.setDefaultMaterialGenerator(matGen);
|
||||||
|
|
||||||
|
TerrainMaterialGenerator::Profile* const activeProfile =
|
||||||
|
mTerrainGlobals.getDefaultMaterialGenerator()
|
||||||
|
->getActiveProfile();
|
||||||
|
mActiveProfile = static_cast<TerrainMaterialGeneratorB::SM2Profile*>(activeProfile);
|
||||||
|
|
||||||
|
//The pixel error should be as high as possible without it being noticed
|
||||||
|
//as it governs how fast mesh quality decreases.
|
||||||
|
mTerrainGlobals.setMaxPixelError(8);
|
||||||
|
|
||||||
|
mTerrainGlobals.setLayerBlendMapSize(32);
|
||||||
|
mTerrainGlobals.setDefaultGlobalColourMapSize(65);
|
||||||
|
|
||||||
|
//10 (default) didn't seem to be quite enough
|
||||||
|
mTerrainGlobals.setSkirtSize(128);
|
||||||
|
|
||||||
|
//due to the sudden flick between composite and non composite textures,
|
||||||
|
//this seemed the distance where it wasn't too noticeable
|
||||||
|
mTerrainGlobals.setCompositeMapDistance(mWorldSize*2);
|
||||||
|
|
||||||
|
mActiveProfile->setLightmapEnabled(false);
|
||||||
|
mActiveProfile->setLayerSpecularMappingEnabled(false);
|
||||||
|
mActiveProfile->setLayerNormalMappingEnabled(false);
|
||||||
|
mActiveProfile->setLayerParallaxMappingEnabled(false);
|
||||||
|
mActiveProfile->setReceiveDynamicShadowsEnabled(false);
|
||||||
|
|
||||||
|
//composite maps lead to a drastic reduction in loading time so are
|
||||||
|
//disabled
|
||||||
|
mActiveProfile->setCompositeMapEnabled(false);
|
||||||
|
|
||||||
|
mTerrainGroup.setOrigin(Vector3(mWorldSize/2,
|
||||||
|
0,
|
||||||
|
-mWorldSize/2));
|
||||||
|
|
||||||
|
Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings();
|
||||||
|
|
||||||
|
importSettings.inputBias = 0;
|
||||||
|
importSettings.terrainSize = mLandSize;
|
||||||
|
importSettings.worldSize = mWorldSize;
|
||||||
|
importSettings.minBatchSize = 9;
|
||||||
|
importSettings.maxBatchSize = mLandSize;
|
||||||
|
|
||||||
|
importSettings.deleteInputData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TerrainManager::~TerrainManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void TerrainManager::setDiffuse(const ColourValue& diffuse)
|
||||||
|
{
|
||||||
|
mTerrainGlobals.setCompositeMapDiffuse(diffuse);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void TerrainManager::setAmbient(const ColourValue& ambient)
|
||||||
|
{
|
||||||
|
mTerrainGlobals.setCompositeMapAmbient(ambient);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store)
|
||||||
|
{
|
||||||
|
const int cellX = store->cell->getGridX();
|
||||||
|
const int cellY = store->cell->getGridY();
|
||||||
|
|
||||||
|
ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY);
|
||||||
|
if ( land != NULL )
|
||||||
|
{
|
||||||
|
land->loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
//split the cell terrain into four segments
|
||||||
|
const int numTextures = ESM::Land::LAND_TEXTURE_SIZE/2;
|
||||||
|
|
||||||
|
for ( int x = 0; x < 2; x++ )
|
||||||
|
{
|
||||||
|
for ( int y = 0; y < 2; y++ )
|
||||||
|
{
|
||||||
|
Terrain::ImportData terrainData =
|
||||||
|
mTerrainGroup.getDefaultImportSettings();
|
||||||
|
|
||||||
|
const int terrainX = cellX * 2 + x;
|
||||||
|
const int terrainY = cellY * 2 + y;
|
||||||
|
|
||||||
|
//it makes far more sense to reallocate the memory here,
|
||||||
|
//and let Ogre deal with it due to the issues with deleting
|
||||||
|
//it at the wrong time if using threads (Which Terrain does)
|
||||||
|
terrainData.inputFloat = OGRE_ALLOC_T(float,
|
||||||
|
mLandSize*mLandSize,
|
||||||
|
MEMCATEGORY_GEOMETRY);
|
||||||
|
|
||||||
|
if ( land != NULL )
|
||||||
|
{
|
||||||
|
//copy the height data row by row
|
||||||
|
for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ )
|
||||||
|
{
|
||||||
|
//the offset of the current segment
|
||||||
|
const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE +
|
||||||
|
//offset of the row
|
||||||
|
terrainCopyY * ESM::Land::LAND_SIZE;
|
||||||
|
const size_t xOffset = x * (mLandSize-1);
|
||||||
|
|
||||||
|
memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize],
|
||||||
|
&land->landData->heights[yOffset + xOffset],
|
||||||
|
mLandSize*sizeof(float));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memset(terrainData.inputFloat, 0, mLandSize*mLandSize*sizeof(float));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<uint16_t, int> indexes;
|
||||||
|
initTerrainTextures(&terrainData, cellX, cellY,
|
||||||
|
x * numTextures, y * numTextures,
|
||||||
|
numTextures, indexes);
|
||||||
|
|
||||||
|
if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL)
|
||||||
|
{
|
||||||
|
mTerrainGroup.defineTerrain(terrainX, terrainY, &terrainData);
|
||||||
|
|
||||||
|
mTerrainGroup.loadTerrain(terrainX, terrainY, true);
|
||||||
|
|
||||||
|
Terrain* terrain = mTerrainGroup.getTerrain(terrainX, terrainY);
|
||||||
|
initTerrainBlendMaps(terrain,
|
||||||
|
cellX, cellY,
|
||||||
|
x * numTextures, y * numTextures,
|
||||||
|
numTextures,
|
||||||
|
indexes);
|
||||||
|
|
||||||
|
if ( land && land->landData->usingColours )
|
||||||
|
{
|
||||||
|
// disable or enable global colour map (depends on available vertex colours)
|
||||||
|
mActiveProfile->setGlobalColourMapEnabled(true);
|
||||||
|
TexturePtr vertex = getVertexColours(land,
|
||||||
|
cellX, cellY,
|
||||||
|
x*(mLandSize-1),
|
||||||
|
y*(mLandSize-1),
|
||||||
|
mLandSize);
|
||||||
|
|
||||||
|
//this is a hack to get around the fact that Ogre seems to
|
||||||
|
//corrupt the global colour map leading to rendering errors
|
||||||
|
MaterialPtr mat = terrain->getMaterial();
|
||||||
|
mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() );
|
||||||
|
//mat = terrain->_getCompositeMapMaterial();
|
||||||
|
//mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mActiveProfile->setGlobalColourMapEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mTerrainGroup.freeTemporaryResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store)
|
||||||
|
{
|
||||||
|
for ( int x = 0; x < 2; x++ )
|
||||||
|
{
|
||||||
|
for ( int y = 0; y < 2; y++ )
|
||||||
|
{
|
||||||
|
mTerrainGroup.unloadTerrain(store->cell->getGridX() * 2 + x,
|
||||||
|
store->cell->getGridY() * 2 + y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData,
|
||||||
|
int cellX, int cellY,
|
||||||
|
int fromX, int fromY, int size,
|
||||||
|
std::map<uint16_t, int>& indexes)
|
||||||
|
{
|
||||||
|
assert(terrainData != NULL && "Must have valid terrain data");
|
||||||
|
assert(fromX >= 0 && fromY >= 0 &&
|
||||||
|
"Can't get a terrain texture on terrain outside the current cell");
|
||||||
|
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||||
|
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||||
|
"Can't get a terrain texture on terrain outside the current cell");
|
||||||
|
|
||||||
|
//this ensures that the ltex indexes are sorted (or retrived as sorted
|
||||||
|
//which simplifies shading between cells).
|
||||||
|
//
|
||||||
|
//If we don't sort the ltex indexes, the splatting order may differ between
|
||||||
|
//cells which may lead to inconsistent results when shading between cells
|
||||||
|
std::set<uint16_t> ltexIndexes;
|
||||||
|
for ( int y = fromY - 1; y < fromY + size + 1; y++ )
|
||||||
|
{
|
||||||
|
for ( int x = fromX - 1; x < fromX + size + 1; x++ )
|
||||||
|
{
|
||||||
|
ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//there is one texture that we want to use as a base (i.e. it won't have
|
||||||
|
//a blend map). This holds the ltex index of that base texture so that
|
||||||
|
//we know not to include it in the output map
|
||||||
|
int baseTexture = -1;
|
||||||
|
for ( std::set<uint16_t>::iterator iter = ltexIndexes.begin();
|
||||||
|
iter != ltexIndexes.end();
|
||||||
|
++iter )
|
||||||
|
{
|
||||||
|
const uint16_t ltexIndex = *iter;
|
||||||
|
//this is the base texture, so we can ignore this at present
|
||||||
|
if ( ltexIndex == baseTexture )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::map<uint16_t, int>::const_iterator it = indexes.find(ltexIndex);
|
||||||
|
|
||||||
|
if ( it == indexes.end() )
|
||||||
|
{
|
||||||
|
//NB: All vtex ids are +1 compared to the ltex ids
|
||||||
|
|
||||||
|
assert( (int)mEnvironment.mWorld->getStore().landTexts.getSize() >= (int)ltexIndex - 1 &&
|
||||||
|
"LAND.VTEX must be within the bounds of the LTEX array");
|
||||||
|
|
||||||
|
std::string texture;
|
||||||
|
if ( ltexIndex == 0 )
|
||||||
|
{
|
||||||
|
texture = "_land_default.dds";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texture = mEnvironment.mWorld->getStore().landTexts.search(ltexIndex-1)->texture;
|
||||||
|
//TODO this is needed due to MWs messed up texture handling
|
||||||
|
texture = texture.substr(0, texture.rfind(".")) + ".dds";
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t position = terrainData->layerList.size();
|
||||||
|
terrainData->layerList.push_back(Terrain::LayerInstance());
|
||||||
|
|
||||||
|
terrainData->layerList[position].worldSize = 256;
|
||||||
|
terrainData->layerList[position].textureNames.push_back("textures\\" + texture);
|
||||||
|
|
||||||
|
if ( baseTexture == -1 )
|
||||||
|
{
|
||||||
|
baseTexture = ltexIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
indexes[ltexIndex] = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void TerrainManager::initTerrainBlendMaps(Terrain* terrain,
|
||||||
|
int cellX, int cellY,
|
||||||
|
int fromX, int fromY, int size,
|
||||||
|
const std::map<uint16_t, int>& indexes)
|
||||||
|
{
|
||||||
|
assert(terrain != NULL && "Must have valid terrain");
|
||||||
|
assert(fromX >= 0 && fromY >= 0 &&
|
||||||
|
"Can't get a terrain texture on terrain outside the current cell");
|
||||||
|
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||||
|
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||||
|
"Can't get a terrain texture on terrain outside the current cell");
|
||||||
|
|
||||||
|
//size must be a power of 2 as we do divisions with a power of 2 number
|
||||||
|
//that need to result in an integer for correct splatting
|
||||||
|
assert( (size & (size - 1)) == 0 && "Size must be a power of 2");
|
||||||
|
|
||||||
|
const int blendMapSize = terrain->getLayerBlendMapSize();
|
||||||
|
const int splatSize = blendMapSize / size;
|
||||||
|
|
||||||
|
//zero out every map
|
||||||
|
std::map<uint16_t, int>::const_iterator iter;
|
||||||
|
for ( iter = indexes.begin(); iter != indexes.end(); ++iter )
|
||||||
|
{
|
||||||
|
float* pBlend = terrain->getLayerBlendMap(iter->second)
|
||||||
|
->getBlendPointer();
|
||||||
|
memset(pBlend, 0, sizeof(float) * blendMapSize * blendMapSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
//covert the ltex data into a set of blend maps
|
||||||
|
for ( int texY = fromY - 1; texY < fromY + size + 1; texY++ )
|
||||||
|
{
|
||||||
|
for ( int texX = fromX - 1; texX < fromX + size + 1; texX++ )
|
||||||
|
{
|
||||||
|
const uint16_t ltexIndex = getLtexIndexAt(cellX, cellY, texX, texY);
|
||||||
|
|
||||||
|
//check if it is the base texture (which isn't in the map) and
|
||||||
|
//if it is don't bother altering the blend map for it
|
||||||
|
if ( indexes.find(ltexIndex) == indexes.end() )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//while texX is the splat index relative to the entire cell,
|
||||||
|
//relX is relative to the current segment we are splatting
|
||||||
|
const int relX = texX - fromX;
|
||||||
|
const int relY = texY - fromY;
|
||||||
|
|
||||||
|
const int layerIndex = indexes.find(ltexIndex)->second;
|
||||||
|
|
||||||
|
float* const pBlend = terrain->getLayerBlendMap(layerIndex)
|
||||||
|
->getBlendPointer();
|
||||||
|
|
||||||
|
for ( int y = -1; y < splatSize + 1; y++ )
|
||||||
|
{
|
||||||
|
for ( int x = -1; x < splatSize + 1; x++ )
|
||||||
|
{
|
||||||
|
|
||||||
|
//Note: Y is reversed
|
||||||
|
const int splatY = blendMapSize - 1 - relY * splatSize - y;
|
||||||
|
const int splatX = relX * splatSize + x;
|
||||||
|
|
||||||
|
if ( splatX >= 0 && splatX < blendMapSize &&
|
||||||
|
splatY >= 0 && splatY < blendMapSize )
|
||||||
|
{
|
||||||
|
const int index = (splatY)*blendMapSize + splatX;
|
||||||
|
|
||||||
|
if ( y >= 0 && y < splatSize &&
|
||||||
|
x >= 0 && x < splatSize )
|
||||||
|
{
|
||||||
|
pBlend[index] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//this provides a transition shading but also
|
||||||
|
//rounds off the corners slightly
|
||||||
|
pBlend[index] = std::min(1.0f, pBlend[index] + 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int i = 1; i < terrain->getLayerCount(); i++ )
|
||||||
|
{
|
||||||
|
TerrainLayerBlendMap* blend = terrain->getLayerBlendMap(i);
|
||||||
|
blend->dirty();
|
||||||
|
blend->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
int TerrainManager::getLtexIndexAt(int cellX, int cellY,
|
||||||
|
int x, int y)
|
||||||
|
{
|
||||||
|
//check texture index falls within the 9 cell bounds
|
||||||
|
//as this function can't cope with anything above that
|
||||||
|
assert(x >= -ESM::Land::LAND_TEXTURE_SIZE &&
|
||||||
|
y >= -ESM::Land::LAND_TEXTURE_SIZE &&
|
||||||
|
"Trying to get land textures that are out of bounds");
|
||||||
|
|
||||||
|
assert(x < 2*ESM::Land::LAND_TEXTURE_SIZE &&
|
||||||
|
y < 2*ESM::Land::LAND_TEXTURE_SIZE &&
|
||||||
|
"Trying to get land textures that are out of bounds");
|
||||||
|
|
||||||
|
if ( x < 0 )
|
||||||
|
{
|
||||||
|
cellX--;
|
||||||
|
x += ESM::Land::LAND_TEXTURE_SIZE;
|
||||||
|
}
|
||||||
|
else if ( x >= ESM::Land::LAND_TEXTURE_SIZE )
|
||||||
|
{
|
||||||
|
cellX++;
|
||||||
|
x -= ESM::Land::LAND_TEXTURE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( y < 0 )
|
||||||
|
{
|
||||||
|
cellY--;
|
||||||
|
y += ESM::Land::LAND_TEXTURE_SIZE;
|
||||||
|
}
|
||||||
|
else if ( y >= ESM::Land::LAND_TEXTURE_SIZE )
|
||||||
|
{
|
||||||
|
cellY++;
|
||||||
|
y -= ESM::Land::LAND_TEXTURE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY);
|
||||||
|
if ( land != NULL )
|
||||||
|
{
|
||||||
|
land->loadData();
|
||||||
|
return land->landData
|
||||||
|
->textures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TexturePtr TerrainManager::getVertexColours(ESM::Land* land,
|
||||||
|
int cellX, int cellY,
|
||||||
|
int fromX, int fromY, int size)
|
||||||
|
{
|
||||||
|
TextureManager* const texMgr = TextureManager::getSingletonPtr();
|
||||||
|
|
||||||
|
const std::string colourTextureName = "VtexColours_" +
|
||||||
|
boost::lexical_cast<std::string>(cellX) +
|
||||||
|
"_" +
|
||||||
|
boost::lexical_cast<std::string>(cellY) +
|
||||||
|
"_" +
|
||||||
|
boost::lexical_cast<std::string>(fromX) +
|
||||||
|
"_" +
|
||||||
|
boost::lexical_cast<std::string>(fromY);
|
||||||
|
|
||||||
|
TexturePtr tex = texMgr->getByName(colourTextureName);
|
||||||
|
if ( !tex.isNull() )
|
||||||
|
{
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
tex = texMgr->createManual(colourTextureName,
|
||||||
|
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||||
|
TEX_TYPE_2D, size, size, 0, PF_BYTE_BGR);
|
||||||
|
|
||||||
|
HardwarePixelBufferSharedPtr pixelBuffer = tex->getBuffer();
|
||||||
|
|
||||||
|
pixelBuffer->lock(HardwareBuffer::HBL_DISCARD);
|
||||||
|
const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
||||||
|
|
||||||
|
uint8* pDest = static_cast<uint8*>(pixelBox.data);
|
||||||
|
|
||||||
|
if ( land != NULL )
|
||||||
|
{
|
||||||
|
const char* const colours = land->landData->colours;
|
||||||
|
for ( int y = 0; y < size; y++ )
|
||||||
|
{
|
||||||
|
for ( int x = 0; x < size; x++ )
|
||||||
|
{
|
||||||
|
const size_t colourOffset = (y+fromY)*3*65 + (x+fromX)*3;
|
||||||
|
|
||||||
|
assert( colourOffset < 65*65*3 &&
|
||||||
|
"Colour offset is out of the expected bounds of record" );
|
||||||
|
|
||||||
|
const unsigned char r = colours[colourOffset + 0];
|
||||||
|
const unsigned char g = colours[colourOffset + 1];
|
||||||
|
const unsigned char b = colours[colourOffset + 2];
|
||||||
|
|
||||||
|
//as is the case elsewhere we need to flip the y
|
||||||
|
const size_t imageOffset = (size - 1 - y)*size*4 + x*4;
|
||||||
|
pDest[imageOffset + 0] = b;
|
||||||
|
pDest[imageOffset + 1] = g;
|
||||||
|
pDest[imageOffset + 2] = r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for ( int y = 0; y < size; y++ )
|
||||||
|
{
|
||||||
|
for ( int x = 0; x < size; x++ )
|
||||||
|
{
|
||||||
|
for ( int k = 0; k < 3; k++ )
|
||||||
|
{
|
||||||
|
*pDest++ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pixelBuffer->unlock();
|
||||||
|
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
#ifndef _GAME_RENDER_TERRAIN_H
|
||||||
|
#define _GAME_RENDER_TERRAIN_H
|
||||||
|
|
||||||
|
#include <OgreTerrain.h>
|
||||||
|
#include <OgreTerrainGroup.h>
|
||||||
|
#include "terrainmaterial.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
|
namespace Ogre{
|
||||||
|
class SceneManager;
|
||||||
|
class TerrainGroup;
|
||||||
|
class TerrainGlobalOptions;
|
||||||
|
class Terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWRender{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the Morrowind terrain using the Ogre Terrain Component
|
||||||
|
*
|
||||||
|
* Each terrain cell is split into four blocks as this leads to an increase
|
||||||
|
* in performance and means we don't hit splat limits quite as much
|
||||||
|
*/
|
||||||
|
class TerrainManager{
|
||||||
|
public:
|
||||||
|
TerrainManager(Ogre::SceneManager* mgr, const MWWorld::Environment& env);
|
||||||
|
virtual ~TerrainManager();
|
||||||
|
|
||||||
|
void setDiffuse(const Ogre::ColourValue& diffuse);
|
||||||
|
void setAmbient(const Ogre::ColourValue& ambient);
|
||||||
|
|
||||||
|
void cellAdded(MWWorld::Ptr::CellStore* store);
|
||||||
|
void cellRemoved(MWWorld::Ptr::CellStore* store);
|
||||||
|
private:
|
||||||
|
Ogre::TerrainGlobalOptions mTerrainGlobals;
|
||||||
|
Ogre::TerrainGroup mTerrainGroup;
|
||||||
|
|
||||||
|
const MWWorld::Environment& mEnvironment;
|
||||||
|
|
||||||
|
Ogre::TerrainMaterialGeneratorB::SM2Profile* mActiveProfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length in verticies of a single terrain block.
|
||||||
|
*/
|
||||||
|
static const int mLandSize = (ESM::Land::LAND_SIZE - 1)/2 + 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length in game units of a single terrain block.
|
||||||
|
*/
|
||||||
|
static const int mWorldSize = ESM::Land::REAL_SIZE/2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setups up the list of textures for part of a cell, using indexes as
|
||||||
|
* an output to create a mapping of MW LtexIndex to the relevant terrain
|
||||||
|
* layer
|
||||||
|
*
|
||||||
|
* @param terrainData the terrain data to setup the textures for
|
||||||
|
* @param cellX the coord of the cell
|
||||||
|
* @param cellY the coord of the cell
|
||||||
|
* @param fromX the ltex index in the current cell to start making the texture from
|
||||||
|
* @param fromY the ltex index in the current cell to start making the texture from
|
||||||
|
* @param size the size (number of splats) to get
|
||||||
|
* @param indexes a mapping of ltex index to the terrain texture layer that
|
||||||
|
* can be used by initTerrainBlendMaps
|
||||||
|
*/
|
||||||
|
void initTerrainTextures(Ogre::Terrain::ImportData* terrainData,
|
||||||
|
int cellX, int cellY,
|
||||||
|
int fromX, int fromY, int size,
|
||||||
|
std::map<uint16_t, int>& indexes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the blend (splatting maps) for the given terrain from the ltex data.
|
||||||
|
*
|
||||||
|
* @param terrain the terrain object for the current cell
|
||||||
|
* @param cellX the coord of the cell
|
||||||
|
* @param cellY the coord of the cell
|
||||||
|
* @param fromX the ltex index in the current cell to start making the texture from
|
||||||
|
* @param fromY the ltex index in the current cell to start making the texture from
|
||||||
|
* @param size the size (number of splats) to get
|
||||||
|
* @param indexes the mapping of ltex to blend map produced by initTerrainTextures
|
||||||
|
*/
|
||||||
|
void initTerrainBlendMaps(Ogre::Terrain* terrain,
|
||||||
|
int cellX, int cellY,
|
||||||
|
int fromX, int fromY, int size,
|
||||||
|
const std::map<uint16_t, int>& indexes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a LTEX index at the given point, assuming the current cell
|
||||||
|
* starts at (0,0). This supports getting values from the surrounding
|
||||||
|
* cells so negative x, y is acceptable
|
||||||
|
*
|
||||||
|
* @param cellX the coord of the cell
|
||||||
|
* @param cellY the coord of the cell
|
||||||
|
* @param x, y the splat position of the ltex index to get relative to the
|
||||||
|
* first splat of the current cell
|
||||||
|
*/
|
||||||
|
int getLtexIndexAt(int cellX, int cellY, int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to the fact that Ogre terrain doesn't support vertex colours
|
||||||
|
* we have to generate them manually
|
||||||
|
*
|
||||||
|
* @param cellX the coord of the cell
|
||||||
|
* @param cellY the coord of the cell
|
||||||
|
* @param fromX the *vertex* index in the current cell to start making texture from
|
||||||
|
* @param fromY the *vertex* index in the current cell to start making the texture from
|
||||||
|
* @param size the size (number of vertexes) to get
|
||||||
|
*/
|
||||||
|
Ogre::TexturePtr getVertexColours(ESM::Land* land,
|
||||||
|
int cellX, int cellY,
|
||||||
|
int fromX, int fromY, int size);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _GAME_RENDER_TERRAIN_H
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
This source file is part of OGRE
|
||||||
|
(Object-oriented Graphics Rendering Engine)
|
||||||
|
For the latest info, see http://www.ogre3d.org/
|
||||||
|
|
||||||
|
Copyright (c) 2000-2011 Torus Knot Software Ltd
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __Ogre_TerrainMaterialGeneratorB_H__
|
||||||
|
#define __Ogre_TerrainMaterialGeneratorB_H__
|
||||||
|
|
||||||
|
#include "OgreTerrainPrerequisites.h"
|
||||||
|
#include "OgreTerrainMaterialGenerator.h"
|
||||||
|
#include "OgreGpuProgramParams.h"
|
||||||
|
|
||||||
|
namespace Ogre
|
||||||
|
{
|
||||||
|
class PSSMShadowCameraSetup;
|
||||||
|
|
||||||
|
/** \addtogroup Optional Components
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
/** \addtogroup Terrain
|
||||||
|
* Some details on the terrain component
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/** A TerrainMaterialGenerator which can cope with normal mapped, specular mapped
|
||||||
|
terrain.
|
||||||
|
@note Requires the Cg plugin to render correctly
|
||||||
|
*/
|
||||||
|
class TerrainMaterialGeneratorB : public TerrainMaterialGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TerrainMaterialGeneratorB();
|
||||||
|
~TerrainMaterialGeneratorB();
|
||||||
|
|
||||||
|
/** Shader model 2 profile target.
|
||||||
|
*/
|
||||||
|
class SM2Profile : public TerrainMaterialGenerator::Profile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc);
|
||||||
|
~SM2Profile();
|
||||||
|
|
||||||
|
bool isVertexCompressionSupported() const {return false;}
|
||||||
|
|
||||||
|
MaterialPtr generate(const Terrain* terrain);
|
||||||
|
MaterialPtr generateForCompositeMap(const Terrain* terrain);
|
||||||
|
uint8 getMaxLayers(const Terrain* terrain) const;
|
||||||
|
void updateParams(const MaterialPtr& mat, const Terrain* terrain);
|
||||||
|
void updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain);
|
||||||
|
void requestOptions(Terrain* terrain);
|
||||||
|
|
||||||
|
/** Whether to support normal mapping per layer in the shader (default true).
|
||||||
|
*/
|
||||||
|
bool isLayerNormalMappingEnabled() const { return mLayerNormalMappingEnabled; }
|
||||||
|
/** Whether to support normal mapping per layer in the shader (default true).
|
||||||
|
*/
|
||||||
|
void setLayerNormalMappingEnabled(bool enabled);
|
||||||
|
/** Whether to support parallax mapping per layer in the shader (default true).
|
||||||
|
*/
|
||||||
|
bool isLayerParallaxMappingEnabled() const { return mLayerParallaxMappingEnabled; }
|
||||||
|
/** Whether to support parallax mapping per layer in the shader (default true).
|
||||||
|
*/
|
||||||
|
void setLayerParallaxMappingEnabled(bool enabled);
|
||||||
|
/** Whether to support specular mapping per layer in the shader (default true).
|
||||||
|
*/
|
||||||
|
bool isLayerSpecularMappingEnabled() const { return mLayerSpecularMappingEnabled; }
|
||||||
|
/** Whether to support specular mapping per layer in the shader (default true).
|
||||||
|
*/
|
||||||
|
void setLayerSpecularMappingEnabled(bool enabled);
|
||||||
|
/** Whether to support a global colour map over the terrain in the shader,
|
||||||
|
if it's present (default true).
|
||||||
|
*/
|
||||||
|
bool isGlobalColourMapEnabled() const { return mGlobalColourMapEnabled; }
|
||||||
|
/** Whether to support a global colour map over the terrain in the shader,
|
||||||
|
if it's present (default true).
|
||||||
|
*/
|
||||||
|
void setGlobalColourMapEnabled(bool enabled);
|
||||||
|
/** Whether to support a light map over the terrain in the shader,
|
||||||
|
if it's present (default true).
|
||||||
|
*/
|
||||||
|
bool isLightmapEnabled() const { return mLightmapEnabled; }
|
||||||
|
/** Whether to support a light map over the terrain in the shader,
|
||||||
|
if it's present (default true).
|
||||||
|
*/
|
||||||
|
void setLightmapEnabled(bool enabled);
|
||||||
|
/** Whether to use the composite map to provide a lower LOD technique
|
||||||
|
in the distance (default true).
|
||||||
|
*/
|
||||||
|
bool isCompositeMapEnabled() const { return mCompositeMapEnabled; }
|
||||||
|
/** Whether to use the composite map to provide a lower LOD technique
|
||||||
|
in the distance (default true).
|
||||||
|
*/
|
||||||
|
void setCompositeMapEnabled(bool enabled);
|
||||||
|
/** Whether to support dynamic texture shadows received from other
|
||||||
|
objects, on the terrain (default true).
|
||||||
|
*/
|
||||||
|
bool getReceiveDynamicShadowsEnabled() const { return mReceiveDynamicShadows; }
|
||||||
|
/** Whether to support dynamic texture shadows received from other
|
||||||
|
objects, on the terrain (default true).
|
||||||
|
*/
|
||||||
|
void setReceiveDynamicShadowsEnabled(bool enabled);
|
||||||
|
|
||||||
|
/** Whether to use PSSM support dynamic texture shadows, and if so the
|
||||||
|
settings to use (default 0).
|
||||||
|
*/
|
||||||
|
void setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings);
|
||||||
|
/** Whether to use PSSM support dynamic texture shadows, and if so the
|
||||||
|
settings to use (default 0).
|
||||||
|
*/
|
||||||
|
PSSMShadowCameraSetup* getReceiveDynamicShadowsPSSM() const { return mPSSM; }
|
||||||
|
/** Whether to use depth shadows (default false).
|
||||||
|
*/
|
||||||
|
void setReceiveDynamicShadowsDepth(bool enabled);
|
||||||
|
/** Whether to use depth shadows (default false).
|
||||||
|
*/
|
||||||
|
bool getReceiveDynamicShadowsDepth() const { return mDepthShadows; }
|
||||||
|
/** Whether to use shadows on low LOD material rendering (when using composite map) (default false).
|
||||||
|
*/
|
||||||
|
void setReceiveDynamicShadowsLowLod(bool enabled);
|
||||||
|
/** Whether to use shadows on low LOD material rendering (when using composite map) (default false).
|
||||||
|
*/
|
||||||
|
bool getReceiveDynamicShadowsLowLod() const { return mLowLodShadows; }
|
||||||
|
|
||||||
|
int getNumberOfLightsSupported() const;
|
||||||
|
|
||||||
|
/// Internal
|
||||||
|
bool _isSM3Available() const { return mSM3Available; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
enum TechniqueType
|
||||||
|
{
|
||||||
|
HIGH_LOD,
|
||||||
|
LOW_LOD,
|
||||||
|
RENDER_COMPOSITE_MAP
|
||||||
|
};
|
||||||
|
void addTechnique(const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt);
|
||||||
|
|
||||||
|
/// Interface definition for helper class to generate shaders
|
||||||
|
class ShaderHelper : public TerrainAlloc
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ShaderHelper() {}
|
||||||
|
virtual ~ShaderHelper() {}
|
||||||
|
virtual HighLevelGpuProgramPtr generateVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
virtual HighLevelGpuProgramPtr generateFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
virtual void updateParams(const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap);
|
||||||
|
protected:
|
||||||
|
virtual String getVertexProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
virtual String getFragmentProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
virtual HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0;
|
||||||
|
virtual HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0;
|
||||||
|
virtual void generateVertexProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
virtual void generateFragmentProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
virtual void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0;
|
||||||
|
virtual void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0;
|
||||||
|
virtual void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0;
|
||||||
|
virtual void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0;
|
||||||
|
virtual void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0;
|
||||||
|
virtual void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0;
|
||||||
|
virtual void defaultVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog);
|
||||||
|
virtual void defaultFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog);
|
||||||
|
virtual void updateVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params);
|
||||||
|
virtual void updateFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params);
|
||||||
|
static String getChannel(uint idx);
|
||||||
|
|
||||||
|
size_t mShadowSamplerStartHi;
|
||||||
|
size_t mShadowSamplerStartLo;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Utility class to help with generating shaders for Cg / HLSL.
|
||||||
|
class ShaderHelperCg : public ShaderHelper
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
uint generateVpDynamicShadowsParams(uint texCoordStart, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateVpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateFpDynamicShadowsHelpers(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateFpDynamicShadowsParams(uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
void generateFpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ShaderHelperHLSL : public ShaderHelperCg
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Utility class to help with generating shaders for GLSL.
|
||||||
|
class ShaderHelperGLSL : public ShaderHelper
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||||
|
void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {}
|
||||||
|
void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {}
|
||||||
|
void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {}
|
||||||
|
void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {}
|
||||||
|
void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {}
|
||||||
|
void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
ShaderHelper* mShaderGen;
|
||||||
|
bool mLayerNormalMappingEnabled;
|
||||||
|
bool mLayerParallaxMappingEnabled;
|
||||||
|
bool mLayerSpecularMappingEnabled;
|
||||||
|
bool mGlobalColourMapEnabled;
|
||||||
|
bool mLightmapEnabled;
|
||||||
|
bool mCompositeMapEnabled;
|
||||||
|
bool mReceiveDynamicShadows;
|
||||||
|
PSSMShadowCameraSetup* mPSSM;
|
||||||
|
bool mDepthShadows;
|
||||||
|
bool mLowLodShadows;
|
||||||
|
bool mSM3Available;
|
||||||
|
|
||||||
|
bool isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,95 @@
|
|||||||
|
#include "water.hpp"
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
|
||||||
|
Water::Water (Ogre::Camera *camera, const ESM::Cell* cell) :
|
||||||
|
mCamera (camera), mViewport (camera->getViewport()), mSceneManager (camera->getSceneManager()),
|
||||||
|
mIsUnderwater(false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Ogre::CompositorManager::getSingleton().addCompositor(mViewport, "Water", -1);
|
||||||
|
Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false);
|
||||||
|
} catch(...) {}
|
||||||
|
|
||||||
|
mTop = cell->water;
|
||||||
|
|
||||||
|
mIsUnderwater = false;
|
||||||
|
|
||||||
|
mWaterPlane = Ogre::Plane(Ogre::Vector3::UNIT_Y, 0);
|
||||||
|
|
||||||
|
Ogre::MeshManager::getSingleton().createPlane("water", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,5, Ogre::Vector3::UNIT_Z);
|
||||||
|
|
||||||
|
mWater = mSceneManager->createEntity("water");
|
||||||
|
|
||||||
|
mWater->setMaterialName("Examples/Water0");
|
||||||
|
|
||||||
|
mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode();
|
||||||
|
mWaterNode->setPosition(0, mTop, 0);
|
||||||
|
|
||||||
|
if(!(cell->data.flags & cell->Interior))
|
||||||
|
{
|
||||||
|
mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY));
|
||||||
|
}
|
||||||
|
mWaterNode->attachObject(mWater);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Water::~Water()
|
||||||
|
{
|
||||||
|
Ogre::MeshManager::getSingleton().remove("water");
|
||||||
|
|
||||||
|
mWaterNode->detachObject(mWater);
|
||||||
|
mSceneManager->destroyEntity(mWater);
|
||||||
|
mSceneManager->destroySceneNode(mWaterNode);
|
||||||
|
|
||||||
|
Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Water::changeCell(const ESM::Cell* cell)
|
||||||
|
{
|
||||||
|
mTop = cell->water;
|
||||||
|
|
||||||
|
if(!(cell->data.flags & cell->Interior))
|
||||||
|
mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY));
|
||||||
|
else
|
||||||
|
setHeight(mTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Water::setHeight(const float height)
|
||||||
|
{
|
||||||
|
mTop = height;
|
||||||
|
mWaterNode->setPosition(0, height, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Water::toggle()
|
||||||
|
{
|
||||||
|
mWater->setVisible(!mWater->getVisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Water::checkUnderwater(float y)
|
||||||
|
{
|
||||||
|
if ((mIsUnderwater && y > mTop) || !mWater->isVisible())
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false);
|
||||||
|
} catch(...) {}
|
||||||
|
mIsUnderwater = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mIsUnderwater && y < mTop && mWater->isVisible())
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", true);
|
||||||
|
} catch(...) {}
|
||||||
|
mIsUnderwater = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ogre::Vector3 Water::getSceneNodeCoordinates(int gridX, int gridY)
|
||||||
|
{
|
||||||
|
return Ogre::Vector3(gridX * CELL_SIZE + (CELL_SIZE / 2), mTop, -gridY * CELL_SIZE - (CELL_SIZE / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
@ -0,0 +1,40 @@
|
|||||||
|
#ifndef GAME_MWRENDER_WATER_H
|
||||||
|
#define GAME_MWRENDER_WATER_H
|
||||||
|
|
||||||
|
#include <Ogre.h>
|
||||||
|
#include <components/esm/loadcell.hpp>
|
||||||
|
|
||||||
|
namespace MWRender {
|
||||||
|
|
||||||
|
/// Water rendering
|
||||||
|
class Water : Ogre::RenderTargetListener, Ogre::Camera::Listener
|
||||||
|
{
|
||||||
|
static const int CELL_SIZE = 8192;
|
||||||
|
Ogre::Camera *mCamera;
|
||||||
|
Ogre::SceneManager *mSceneManager;
|
||||||
|
Ogre::Viewport *mViewport;
|
||||||
|
|
||||||
|
Ogre::Plane mWaterPlane;
|
||||||
|
Ogre::SceneNode *mWaterNode;
|
||||||
|
Ogre::Entity *mWater;
|
||||||
|
|
||||||
|
bool mIsUnderwater;
|
||||||
|
int mTop;
|
||||||
|
|
||||||
|
Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Water (Ogre::Camera *camera, const ESM::Cell* cell);
|
||||||
|
~Water();
|
||||||
|
|
||||||
|
void toggle();
|
||||||
|
|
||||||
|
void checkUnderwater(float y);
|
||||||
|
void changeCell(const ESM::Cell* cell);
|
||||||
|
void setHeight(const float height);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,13 @@
|
|||||||
|
project(resources)
|
||||||
|
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/caustic_0.png "${OpenMW_BINARY_DIR}/resources/water/caustic_0.png" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Example_Fresnel.cg "${OpenMW_BINARY_DIR}/resources/water/Example_Fresnel.cg" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Example_FresnelPS.asm "${OpenMW_BINARY_DIR}/resources/water/Example_FresnelPS.asm" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/GlassFP.cg "${OpenMW_BINARY_DIR}/resources/water/GlassFP.cg" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/GlassVP.cg "${OpenMW_BINARY_DIR}/resources/water/GlassVP.cg" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/perlinvolume.dds "${OpenMW_BINARY_DIR}/resources/water/perlinvolume.dds" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Water02.jpg "${OpenMW_BINARY_DIR}/resources/water/Water02.jpg" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.compositor "${OpenMW_BINARY_DIR}/resources/water/water.compositor" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/waves2.dds "${OpenMW_BINARY_DIR}/resources/water/waves2.dds" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Examples-Water.material "${OpenMW_BINARY_DIR}/resources/water/Examples-Water.material" COPYONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/WaterNormal1.tga "${OpenMW_BINARY_DIR}/resources/water/WaterNormal1.tga" COPYONLY)
|
Binary file not shown.
@ -0,0 +1,116 @@
|
|||||||
|
// Vertex program for fresnel reflections / refractions
|
||||||
|
void main_vp(
|
||||||
|
float4 pos : POSITION,
|
||||||
|
float4 normal : NORMAL,
|
||||||
|
float2 tex : TEXCOORD0,
|
||||||
|
|
||||||
|
out float4 oPos : POSITION,
|
||||||
|
out float3 noiseCoord : TEXCOORD0,
|
||||||
|
out float4 projectionCoord : TEXCOORD1,
|
||||||
|
out float3 oEyeDir : TEXCOORD2,
|
||||||
|
out float3 oNormal : TEXCOORD3,
|
||||||
|
|
||||||
|
uniform float4x4 worldViewProjMatrix,
|
||||||
|
uniform float3 eyePosition, // object space
|
||||||
|
uniform float timeVal,
|
||||||
|
uniform float scale, // the amount to scale the noise texture by
|
||||||
|
uniform float scroll, // the amount by which to scroll the noise
|
||||||
|
uniform float noise // the noise perturb as a factor of the time
|
||||||
|
)
|
||||||
|
{
|
||||||
|
oPos = mul(worldViewProjMatrix, pos);
|
||||||
|
// Projective texture coordinates, adjust for mapping
|
||||||
|
float4x4 scalemat = float4x4(0.5, 0, 0, 0.5,
|
||||||
|
0,-0.5, 0, 0.5,
|
||||||
|
0, 0, 0.5, 0.5,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
projectionCoord = mul(scalemat, oPos);
|
||||||
|
// Noise map coords
|
||||||
|
noiseCoord.xy = (tex + (timeVal * scroll)) * scale;
|
||||||
|
noiseCoord.z = noise * timeVal;
|
||||||
|
|
||||||
|
oEyeDir = normalize(pos.xyz - eyePosition);
|
||||||
|
oNormal = normal.rgb;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fragment program for distorting a texture using a 3D noise texture
|
||||||
|
void main_fp(
|
||||||
|
float3 noiseCoord : TEXCOORD0,
|
||||||
|
float4 projectionCoord : TEXCOORD1,
|
||||||
|
float3 eyeDir : TEXCOORD2,
|
||||||
|
float3 normal : TEXCOORD3,
|
||||||
|
|
||||||
|
out float4 col : COLOR,
|
||||||
|
|
||||||
|
uniform float4 tintColour,
|
||||||
|
uniform float noiseScale,
|
||||||
|
uniform float fresnelBias,
|
||||||
|
uniform float fresnelScale,
|
||||||
|
uniform float fresnelPower,
|
||||||
|
uniform sampler2D waterTex : register(s0),
|
||||||
|
uniform sampler2D noiseMap : register(s1),
|
||||||
|
uniform sampler2D reflectMap : register(s2),
|
||||||
|
uniform sampler2D refractMap : register(s3)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Do the tex projection manually so we can distort _after_
|
||||||
|
float2 final = projectionCoord.xy / projectionCoord.w;
|
||||||
|
|
||||||
|
// Noise
|
||||||
|
float3 noiseNormal = (tex2D(noiseMap, (noiseCoord.xy / 5)).rgb - 0.5).rbg * noiseScale;
|
||||||
|
final += noiseNormal.xz;
|
||||||
|
|
||||||
|
// Fresnel
|
||||||
|
//normal = normalize(normal + noiseNormal.xz);
|
||||||
|
float fresnel = fresnelBias + fresnelScale * pow(1 + dot(eyeDir, normal), fresnelPower);
|
||||||
|
|
||||||
|
// Reflection / refraction
|
||||||
|
float4 reflectionColour = tex2D(reflectMap, final);
|
||||||
|
float4 refractionColour = tex2D(refractMap, final) + tintColour;
|
||||||
|
|
||||||
|
// Final colour
|
||||||
|
col = lerp(refractionColour, reflectionColour, fresnel) * tex2D(waterTex, noiseNormal) / 3 ;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Old version to match ATI PS 1.3 implementation
|
||||||
|
void main_vp_old(
|
||||||
|
float4 pos : POSITION,
|
||||||
|
float4 normal : NORMAL,
|
||||||
|
float2 tex : TEXCOORD0,
|
||||||
|
|
||||||
|
out float4 oPos : POSITION,
|
||||||
|
out float fresnel : COLOR,
|
||||||
|
out float3 noiseCoord : TEXCOORD0,
|
||||||
|
out float4 projectionCoord : TEXCOORD1,
|
||||||
|
|
||||||
|
uniform float4x4 worldViewProjMatrix,
|
||||||
|
uniform float3 eyePosition, // object space
|
||||||
|
uniform float fresnelBias,
|
||||||
|
uniform float fresnelScale,
|
||||||
|
uniform float fresnelPower,
|
||||||
|
uniform float timeVal,
|
||||||
|
uniform float scale, // the amount to scale the noise texture by
|
||||||
|
uniform float scroll, // the amount by which to scroll the noise
|
||||||
|
uniform float noise // the noise perturb as a factor of the time
|
||||||
|
)
|
||||||
|
{
|
||||||
|
oPos = mul(worldViewProjMatrix, pos);
|
||||||
|
// Projective texture coordinates, adjust for mapping
|
||||||
|
float4x4 scalemat = float4x4(0.5, 0, 0, 0.5,
|
||||||
|
0,-0.5, 0, 0.5,
|
||||||
|
0, 0, 0.5, 0.5,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
projectionCoord = mul(scalemat, oPos);
|
||||||
|
// Noise map coords
|
||||||
|
noiseCoord.xy = (tex + (timeVal * scroll)) * scale;
|
||||||
|
noiseCoord.z = noise * timeVal;
|
||||||
|
|
||||||
|
// calc fresnel factor (reflection coefficient)
|
||||||
|
float3 eyeDir = normalize(pos.xyz - eyePosition);
|
||||||
|
fresnel = fresnelBias + fresnelScale * pow(1 + dot(eyeDir, normal), fresnelPower);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
ps.1.4
|
||||||
|
// conversion from Cg generated ARB_fragment_program to ps.1.4 by NFZ
|
||||||
|
// command line args: -profile arbfp1 -entry main_fp
|
||||||
|
// program main_fp
|
||||||
|
// c0 : distortionRange
|
||||||
|
// c1 : tintColour
|
||||||
|
// testure 0 : noiseMap
|
||||||
|
// texture 1 : reflectMap
|
||||||
|
// texture 2 : refractMap
|
||||||
|
// v0.x : fresnel
|
||||||
|
// t0.xyz : noiseCoord
|
||||||
|
// t1.xyw : projectionCoord
|
||||||
|
|
||||||
|
def c2, 2, 1, 0, 0
|
||||||
|
|
||||||
|
// Cg: distort.x = tex3D(noiseMap, noiseCoord).x;
|
||||||
|
// arbfp1: TEX R0.x, fragment.texcoord[0], texture[0], 3D;
|
||||||
|
// sample noise map using noiseCoord in TEX unit 0
|
||||||
|
|
||||||
|
texld r0, t0.xyz
|
||||||
|
|
||||||
|
// get projected texture coordinates from TEX coord 1
|
||||||
|
// will be used in phase 2
|
||||||
|
|
||||||
|
texcrd r1.xy, t1_dw.xyw
|
||||||
|
mov r1.z, c2.y
|
||||||
|
|
||||||
|
// Cg: distort.y = tex3D(noiseMap, noiseCoord + yoffset).x;
|
||||||
|
// arbfp1: ADD R1.xyz, fragment.texcoord[0], c1;
|
||||||
|
// arbfp1: TEX R1.x, R1, texture[0], 3D;
|
||||||
|
// arbfp1: MOV R0.y, R1.x;
|
||||||
|
|
||||||
|
// Cg: distort = (distort * 2 - 1) * distortionRange;
|
||||||
|
// arbfp1: MAD R0.xy, R0, c0.x, -c0.y;
|
||||||
|
// arbfp1: MUL R0.xy, R0, u0.x;
|
||||||
|
// (distort * 2 - 1) same as 2*(distort -.5) so use _bx2
|
||||||
|
|
||||||
|
|
||||||
|
// Cg: final = projectionCoord.xy / projectionCoord.w;
|
||||||
|
// Cg: final += distort;
|
||||||
|
// arbfp1: RCP R0.w, fragment.texcoord[1].w;
|
||||||
|
// arbfp1: MAD R0.xy, fragment.texcoord[1], R0.w, R0;
|
||||||
|
// final = (distort * projectionCoord.w) + projectionCoord.xy
|
||||||
|
// for ps.1.4 have to re-arrange things a bit to perturb projected texture coordinates
|
||||||
|
|
||||||
|
mad r0.xyz, r0_bx2, c0.x, r1
|
||||||
|
|
||||||
|
phase
|
||||||
|
|
||||||
|
// do dependant texture reads
|
||||||
|
// Cg: reflectionColour = tex2D(reflectMap, final);
|
||||||
|
// arbfp1: TEX R0, R0, texture[1], 2D;
|
||||||
|
// sampe reflectMap using dependant read : texunit 1
|
||||||
|
|
||||||
|
texld r1, r0.xyz
|
||||||
|
|
||||||
|
// Cg: refractionColour = tex2D(refractMap, final) + tintColour;
|
||||||
|
// arbfp1: TEX R1, R0, texture[2], 2D;
|
||||||
|
// sample refractMap : texunit 2
|
||||||
|
|
||||||
|
texld r2, r0.xyz
|
||||||
|
|
||||||
|
// adding tintColour that is in global c1
|
||||||
|
// arbfp1: ADD R1, R1, u1;
|
||||||
|
|
||||||
|
add r2, r2, c1
|
||||||
|
|
||||||
|
// Cg: col = lerp(refractionColour, reflectionColour, fresnel);
|
||||||
|
// arbfp1: ADD R0, R0, -R1;
|
||||||
|
// arbfp1: MAD result.color, fragment.color.primary.x, R0, R1;
|
||||||
|
|
||||||
|
lrp r0, v0.x, r1, r2
|
@ -0,0 +1,149 @@
|
|||||||
|
|
||||||
|
vertex_program Water/GlassVP cg
|
||||||
|
{
|
||||||
|
source GlassVP.cg
|
||||||
|
entry_point glass_vp
|
||||||
|
profiles vs_1_1 arbvp1
|
||||||
|
|
||||||
|
default_params
|
||||||
|
{
|
||||||
|
param_named_auto worldViewProj worldviewproj_matrix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fragment_program Water/GlassFP cg
|
||||||
|
{
|
||||||
|
source GlassFP.cg
|
||||||
|
entry_point main_ps
|
||||||
|
profiles ps_2_0 arbfp1
|
||||||
|
}
|
||||||
|
|
||||||
|
material Water/Compositor
|
||||||
|
{
|
||||||
|
technique
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
depth_check off
|
||||||
|
vertex_program_ref Water/GlassVP
|
||||||
|
{
|
||||||
|
param_named_auto timeVal time 0.25
|
||||||
|
param_named scale float 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment_program_ref Water/GlassFP
|
||||||
|
{
|
||||||
|
param_named tintColour float4 0 0.35 0.35 1
|
||||||
|
}
|
||||||
|
|
||||||
|
texture_unit RT
|
||||||
|
{
|
||||||
|
tex_coord_set 0
|
||||||
|
tex_address_mode clamp
|
||||||
|
filtering linear linear linear
|
||||||
|
}
|
||||||
|
|
||||||
|
texture_unit
|
||||||
|
{
|
||||||
|
texture WaterNormal1.tga 2d
|
||||||
|
tex_coord_set 1
|
||||||
|
//tex_address_mode clamp
|
||||||
|
filtering linear linear linear
|
||||||
|
}
|
||||||
|
texture_unit
|
||||||
|
{
|
||||||
|
texture caustic_0.png 2d
|
||||||
|
tex_coord_set 2
|
||||||
|
//tex_address_mode clamp
|
||||||
|
filtering linear linear linear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vertex_program Water/RefractReflectVP cg
|
||||||
|
{
|
||||||
|
source Example_Fresnel.cg
|
||||||
|
entry_point main_vp
|
||||||
|
profiles vs_1_1 arbvp1
|
||||||
|
}
|
||||||
|
vertex_program Water/RefractReflectVPold cg
|
||||||
|
{
|
||||||
|
source Example_Fresnel.cg
|
||||||
|
entry_point main_vp_old
|
||||||
|
profiles vs_1_1 arbvp1
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment_program Water/RefractReflectFP cg
|
||||||
|
{
|
||||||
|
source Example_Fresnel.cg
|
||||||
|
entry_point main_fp
|
||||||
|
// sorry, ps_1_1 and fp20 can't do this
|
||||||
|
profiles ps_2_0 arbfp1
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment_program Water/RefractReflectPS asm
|
||||||
|
{
|
||||||
|
source Example_FresnelPS.asm
|
||||||
|
// sorry, only for ps_1_4 :)
|
||||||
|
syntax ps_1_4
|
||||||
|
|
||||||
|
}
|
||||||
|
material Examples/Water0
|
||||||
|
{
|
||||||
|
|
||||||
|
technique
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
//
|
||||||
|
|
||||||
|
depth_write off
|
||||||
|
vertex_program_ref Water/RefractReflectVP
|
||||||
|
{
|
||||||
|
param_named_auto worldViewProjMatrix worldviewproj_matrix
|
||||||
|
param_named_auto eyePosition camera_position_object_space
|
||||||
|
param_named_auto timeVal time 0.15
|
||||||
|
param_named scroll float 1
|
||||||
|
param_named scale float 1
|
||||||
|
param_named noise float 1
|
||||||
|
// scroll and noisePos will need updating per frame
|
||||||
|
}
|
||||||
|
fragment_program_ref Water/RefractReflectFP
|
||||||
|
{
|
||||||
|
param_named fresnelBias float -0.1
|
||||||
|
param_named fresnelScale float 0.8
|
||||||
|
param_named fresnelPower float 20
|
||||||
|
param_named tintColour float4 1 1 1 1
|
||||||
|
param_named noiseScale float 0.05
|
||||||
|
}
|
||||||
|
// Water
|
||||||
|
scene_blend alpha_blend
|
||||||
|
texture_unit
|
||||||
|
{
|
||||||
|
|
||||||
|
// Water texture
|
||||||
|
texture Water02.jpg
|
||||||
|
// min / mag filtering, no mip
|
||||||
|
filtering linear linear none
|
||||||
|
alpha_op_ex source1 src_manual src_current 0.9
|
||||||
|
|
||||||
|
}
|
||||||
|
// Noise
|
||||||
|
texture_unit
|
||||||
|
{
|
||||||
|
alpha_op_ex source1 src_manual src_current 0.9
|
||||||
|
// Perlin noise volume
|
||||||
|
texture waves2.dds
|
||||||
|
// min / mag filtering, no mip
|
||||||
|
filtering linear linear none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
sampler RT : register(s0);
|
||||||
|
sampler NormalMap : register(s1);
|
||||||
|
sampler CausticMap : register(s2);
|
||||||
|
|
||||||
|
float4 main_ps(float2 iTexCoord : TEXCOORD0,
|
||||||
|
float3 noiseCoord : TEXCOORD1,
|
||||||
|
uniform float4 tintColour) : COLOR
|
||||||
|
{
|
||||||
|
float4 normal = tex2D(NormalMap, noiseCoord);
|
||||||
|
|
||||||
|
|
||||||
|
return tex2D(RT, iTexCoord + normal.xy * 0.05) +
|
||||||
|
(tex2D(CausticMap, noiseCoord) / 5) +
|
||||||
|
tintColour ;
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
void glass_vp
|
||||||
|
(
|
||||||
|
in float4 inPos : POSITION,
|
||||||
|
|
||||||
|
out float4 pos : POSITION,
|
||||||
|
out float2 uv0 : TEXCOORD0,
|
||||||
|
out float4 noiseCoord : TEXCOORD1,
|
||||||
|
|
||||||
|
uniform float4x4 worldViewProj,
|
||||||
|
uniform float timeVal,
|
||||||
|
uniform float scale
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Use standardise transform, so work accord with render system specific (RS depth, requires texture flipping, etc)
|
||||||
|
pos = mul(worldViewProj, inPos);
|
||||||
|
|
||||||
|
// The input positions adjusted by texel offsets, so clean up inaccuracies
|
||||||
|
inPos.xy = sign(inPos.xy);
|
||||||
|
|
||||||
|
// Convert to image-space
|
||||||
|
uv0 = (float2(inPos.x, -inPos.y) + 1.0f) * 0.5f;
|
||||||
|
noiseCoord = (pos + timeVal) * scale;
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 182 KiB |
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
@ -0,0 +1,21 @@
|
|||||||
|
compositor Water
|
||||||
|
{
|
||||||
|
technique
|
||||||
|
{
|
||||||
|
texture rt0 target_width target_height PF_R8G8B8
|
||||||
|
|
||||||
|
target rt0 { input previous }
|
||||||
|
|
||||||
|
target_output
|
||||||
|
{
|
||||||
|
// Start with clear output
|
||||||
|
input none
|
||||||
|
|
||||||
|
pass render_quad
|
||||||
|
{
|
||||||
|
material Water/Compositor
|
||||||
|
input 0 rt0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Loading…
Reference in New Issue