Implement instanced groundcover
parent
f917037ead
commit
14cf0ce1dc
@ -0,0 +1,264 @@
|
|||||||
|
#include "groundcover.hpp"
|
||||||
|
|
||||||
|
#include <osg/Geometry>
|
||||||
|
#include <osg/VertexAttribDivisor>
|
||||||
|
|
||||||
|
#include <components/esm/esmreader.hpp>
|
||||||
|
|
||||||
|
#include "apps/openmw/mwworld/esmstore.hpp"
|
||||||
|
#include "apps/openmw/mwbase/environment.hpp"
|
||||||
|
#include "apps/openmw/mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "vismask.hpp"
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
void GroundcoverUpdater::setWindSpeed(float windSpeed)
|
||||||
|
{
|
||||||
|
mWindSpeed = windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos)
|
||||||
|
{
|
||||||
|
mPlayerPos = playerPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroundcoverUpdater::setDefaults(osg::StateSet *stateset)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Uniform> windUniform = new osg::Uniform("windSpeed", 0.0f);
|
||||||
|
stateset->addUniform(windUniform.get());
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Uniform> playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f));
|
||||||
|
stateset->addUniform(playerPosUniform.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Uniform> windUniform = stateset->getUniform("windSpeed");
|
||||||
|
if (windUniform != nullptr)
|
||||||
|
windUniform->set(mWindSpeed);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Uniform> playerPosUniform = stateset->getUniform("playerPos");
|
||||||
|
if (playerPosUniform != nullptr)
|
||||||
|
playerPosUniform->set(mPlayerPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstancingVisitor : public osg::NodeVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InstancingVisitor(std::vector<Groundcover::GroundcoverEntry>& instances, osg::Vec3f& chunkPosition)
|
||||||
|
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||||
|
, mInstances(instances)
|
||||||
|
, mChunkPosition(chunkPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply(osg::Node& node) override
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::StateSet> ss = node.getStateSet();
|
||||||
|
if (ss != nullptr)
|
||||||
|
{
|
||||||
|
removeAlpha(ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply(osg::Geometry& geom) override
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i)
|
||||||
|
{
|
||||||
|
geom.getPrimitiveSet(i)->setNumInstances(mInstances.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Vec4Array> transforms = new osg::Vec4Array(mInstances.size());
|
||||||
|
osg::BoundingBox box;
|
||||||
|
float radius = geom.getBoundingBox().radius();
|
||||||
|
for (unsigned int i = 0; i < transforms->getNumElements(); i++)
|
||||||
|
{
|
||||||
|
osg::Vec3f pos(mInstances[i].mPos.asVec3());
|
||||||
|
osg::Vec3f relativePos = pos - mChunkPosition;
|
||||||
|
(*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale);
|
||||||
|
|
||||||
|
// Use an additional margin due to groundcover animation
|
||||||
|
float instanceRadius = radius * mInstances[i].mScale * 1.1f;
|
||||||
|
osg::BoundingSphere instanceBounds(relativePos, instanceRadius);
|
||||||
|
box.expandBy(instanceBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
geom.setInitialBound(box);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Vec3Array> rotations = new osg::Vec3Array(mInstances.size());
|
||||||
|
for (unsigned int i = 0; i < rotations->getNumElements(); i++)
|
||||||
|
{
|
||||||
|
(*rotations)[i] = mInstances[i].mPos.asRotationVec3();
|
||||||
|
}
|
||||||
|
|
||||||
|
geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX);
|
||||||
|
geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::StateSet> ss = geom.getOrCreateStateSet();
|
||||||
|
ss->setAttribute(new osg::VertexAttribDivisor(6, 1));
|
||||||
|
ss->setAttribute(new osg::VertexAttribDivisor(7, 1));
|
||||||
|
|
||||||
|
removeAlpha(ss);
|
||||||
|
|
||||||
|
traverse(geom);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<Groundcover::GroundcoverEntry> mInstances;
|
||||||
|
osg::Vec3f mChunkPosition;
|
||||||
|
|
||||||
|
void removeAlpha(osg::StateSet* stateset)
|
||||||
|
{
|
||||||
|
// MGE uses default alpha settings for groundcover, so we can not rely on alpha properties
|
||||||
|
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
|
||||||
|
stateset->removeMode(GL_ALPHA_TEST);
|
||||||
|
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
|
||||||
|
stateset->removeMode(GL_BLEND);
|
||||||
|
stateset->setRenderBinToInherit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DensityCalculator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DensityCalculator(float density)
|
||||||
|
: mDensity(density)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInstanceEnabled()
|
||||||
|
{
|
||||||
|
if (mDensity >= 1.f) return true;
|
||||||
|
|
||||||
|
mCurrentGroundcover += mDensity;
|
||||||
|
if (mCurrentGroundcover < 1.f) return false;
|
||||||
|
|
||||||
|
mCurrentGroundcover -= 1.f;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void reset() { mCurrentGroundcover = 0.f; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
float mCurrentGroundcover = 0.f;
|
||||||
|
float mDensity = 0.f;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound)
|
||||||
|
{
|
||||||
|
osg::Vec2f size = maxBound - minBound;
|
||||||
|
if (size.x() >=1 && size.y() >=1) return true;
|
||||||
|
|
||||||
|
osg::Vec3f pos = ref.mPos.asVec3();
|
||||||
|
osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE;
|
||||||
|
if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y())
|
||||||
|
|| (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||||
|
{
|
||||||
|
ChunkId id = std::make_tuple(center, size, activeGrid);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
|
||||||
|
if (obj)
|
||||||
|
return obj->asNode();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InstanceMap instances;
|
||||||
|
collectInstances(instances, size, center);
|
||||||
|
osg::ref_ptr<osg::Node> node = createChunk(instances, center);
|
||||||
|
mCache->addEntryToObjectCache(id, node.get());
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density)
|
||||||
|
: GenericResourceManager<ChunkId>(nullptr)
|
||||||
|
, mSceneManager(sceneManager)
|
||||||
|
, mDensity(density)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center)
|
||||||
|
{
|
||||||
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f));
|
||||||
|
osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
|
||||||
|
DensityCalculator calculator(mDensity);
|
||||||
|
std::vector<ESM::ESMReader> esm;
|
||||||
|
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
|
||||||
|
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
|
||||||
|
{
|
||||||
|
for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
|
||||||
|
{
|
||||||
|
const ESM::Cell* cell = store.get<ESM::Cell>().searchStatic(cellX, cellY);
|
||||||
|
if (!cell) continue;
|
||||||
|
|
||||||
|
calculator.reset();
|
||||||
|
for (size_t i=0; i<cell->mContextList.size(); ++i)
|
||||||
|
{
|
||||||
|
unsigned int index = cell->mContextList.at(i).index;
|
||||||
|
if (esm.size() <= index)
|
||||||
|
esm.resize(index+1);
|
||||||
|
cell->restore(esm[index], i);
|
||||||
|
ESM::CellRef ref;
|
||||||
|
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
|
||||||
|
bool deleted = false;
|
||||||
|
while(cell->getNextRef(esm[index], ref, deleted))
|
||||||
|
{
|
||||||
|
if (deleted) continue;
|
||||||
|
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
||||||
|
std::string model;
|
||||||
|
if (!store.isGroundcover(ref.mRefID, model)) continue;
|
||||||
|
if (model.empty()) continue;
|
||||||
|
|
||||||
|
if (!calculator.isInstanceEnabled()) continue;
|
||||||
|
if (!isInChunkBorders(ref, minBound, maxBound)) continue;
|
||||||
|
|
||||||
|
model = "meshes/" + model;
|
||||||
|
instances[model].emplace_back(ref, model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Group> group = new osg::Group;
|
||||||
|
osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE;
|
||||||
|
for (auto& pair : instances)
|
||||||
|
{
|
||||||
|
const osg::Node* temp = mSceneManager->getTemplate(pair.first);
|
||||||
|
osg::ref_ptr<osg::Node> node = static_cast<osg::Node*>(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES)));
|
||||||
|
|
||||||
|
// Keep link to original mesh to keep it in cache
|
||||||
|
group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp));
|
||||||
|
|
||||||
|
InstancingVisitor visitor(pair.second, worldCenter);
|
||||||
|
node->accept(visitor);
|
||||||
|
group->addChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
group->getBound();
|
||||||
|
group->setNodeMask(Mask_Groundcover);
|
||||||
|
mSceneManager->recreateShaders(group, "groundcover", false, true);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int Groundcover::getNodeMask()
|
||||||
|
{
|
||||||
|
return Mask_Groundcover;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||||
|
{
|
||||||
|
stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
#ifndef OPENMW_MWRENDER_GROUNDCOVER_H
|
||||||
|
#define OPENMW_MWRENDER_GROUNDCOVER_H
|
||||||
|
|
||||||
|
#include <components/terrain/quadtreeworld.hpp>
|
||||||
|
#include <components/resource/scenemanager.hpp>
|
||||||
|
#include <components/sceneutil/statesetupdater.hpp>
|
||||||
|
#include <components/esm/loadcell.hpp>
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
class GroundcoverUpdater : public SceneUtil::StateSetUpdater
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroundcoverUpdater()
|
||||||
|
: mWindSpeed(0.f)
|
||||||
|
, mPlayerPos(osg::Vec3f())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWindSpeed(float windSpeed);
|
||||||
|
void setPlayerPos(osg::Vec3f playerPos);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setDefaults(osg::StateSet *stateset) override;
|
||||||
|
void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float mWindSpeed;
|
||||||
|
osg::Vec3f mPlayerPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::tuple<osg::Vec2f, float, bool> ChunkId; // Center, Size, ActiveGrid
|
||||||
|
class Groundcover : public Resource::GenericResourceManager<ChunkId>, public Terrain::QuadTreeWorld::ChunkManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Groundcover(Resource::SceneManager* sceneManager, float density);
|
||||||
|
~Groundcover() = default;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||||
|
|
||||||
|
unsigned int getNodeMask() override;
|
||||||
|
|
||||||
|
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
|
||||||
|
|
||||||
|
struct GroundcoverEntry
|
||||||
|
{
|
||||||
|
ESM::Position mPos;
|
||||||
|
float mScale;
|
||||||
|
std::string mModel;
|
||||||
|
|
||||||
|
GroundcoverEntry(const ESM::CellRef& ref, const std::string& model)
|
||||||
|
{
|
||||||
|
mPos = ref.mPos;
|
||||||
|
mScale = ref.mScale;
|
||||||
|
mModel = model;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Resource::SceneManager* mSceneManager;
|
||||||
|
float mDensity;
|
||||||
|
|
||||||
|
typedef std::map<std::string, std::vector<GroundcoverEntry>> InstanceMap;
|
||||||
|
osg::ref_ptr<osg::Node> createChunk(InstanceMap& instances, const osg::Vec2f& center);
|
||||||
|
void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,68 @@
|
|||||||
|
Groundcover Settings
|
||||||
|
####################
|
||||||
|
|
||||||
|
enabled
|
||||||
|
-------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Allows the engine to use groundcover.
|
||||||
|
Groundcover objects are static objects which come from ESP files, registered via
|
||||||
|
"groundcover" entries from openmw.cfg rather than "content" ones.
|
||||||
|
We assume that groundcover objects have no collisions, can not be moved or interacted with,
|
||||||
|
so we can merge them to pages and animate them indifferently from distance from player.
|
||||||
|
|
||||||
|
This setting can only be configured by editing the settings configuration file.
|
||||||
|
|
||||||
|
fade start
|
||||||
|
----------
|
||||||
|
|
||||||
|
:Type: floating point
|
||||||
|
:Range: 0.0 to 1.0
|
||||||
|
:Default: 0.85
|
||||||
|
|
||||||
|
Determines on which distance from player groundcover fading starts.
|
||||||
|
Does not work for meshes which textures do not have transparency (e.g. rocks).
|
||||||
|
|
||||||
|
This setting can only be configured by editing the settings configuration file.
|
||||||
|
|
||||||
|
density
|
||||||
|
-------
|
||||||
|
|
||||||
|
:Type: floating point
|
||||||
|
:Range: 0.0 (0%) to 1.0 (100%)
|
||||||
|
:Default: 1.0
|
||||||
|
|
||||||
|
Determines how many groundcover instances from content files
|
||||||
|
are used in the game. Can affect performance a lot.
|
||||||
|
|
||||||
|
This setting can only be configured by editing the settings configuration file.
|
||||||
|
|
||||||
|
distance
|
||||||
|
--------
|
||||||
|
|
||||||
|
:Type: integer
|
||||||
|
:Range: > 0
|
||||||
|
:Default: 1
|
||||||
|
|
||||||
|
Determines on which distance in cells grass pages are rendered.
|
||||||
|
Default 1 value means 3x3 cells area (active grid).
|
||||||
|
May affect performance a lot.
|
||||||
|
|
||||||
|
This setting can only be configured by editing the settings configuration file.
|
||||||
|
|
||||||
|
min chunk size
|
||||||
|
--------------
|
||||||
|
|
||||||
|
:Type: floating point
|
||||||
|
:Range: 0.125, 0.25, 0.5, 1.0
|
||||||
|
:Default: 0.5
|
||||||
|
|
||||||
|
Determines a minimum size of groundcover chunks in cells. For example, with 0.5 value
|
||||||
|
chunks near player will have size 4096x4096 game units. Larger chunks reduce CPU usage
|
||||||
|
(Draw and Cull bars), but can increase GPU usage (GPU bar) since culling becomes less efficient.
|
||||||
|
Smaller values do an opposite.
|
||||||
|
|
||||||
|
This setting can only be configured by editing the settings configuration file.
|
@ -0,0 +1,93 @@
|
|||||||
|
#version 120
|
||||||
|
|
||||||
|
#define GROUNDCOVER
|
||||||
|
|
||||||
|
#if @diffuseMap
|
||||||
|
uniform sampler2D diffuseMap;
|
||||||
|
varying vec2 diffuseMapUV;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if @normalMap
|
||||||
|
uniform sampler2D normalMap;
|
||||||
|
varying vec2 normalMapUV;
|
||||||
|
varying vec4 passTangent;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting.
|
||||||
|
// They may do not look as intended with per-pixel lighting, so ignore this setting for now.
|
||||||
|
#define PER_PIXEL_LIGHTING @normalMap
|
||||||
|
|
||||||
|
varying float euclideanDepth;
|
||||||
|
varying float linearDepth;
|
||||||
|
|
||||||
|
#if PER_PIXEL_LIGHTING
|
||||||
|
varying vec3 passViewPos;
|
||||||
|
varying vec3 passNormal;
|
||||||
|
#else
|
||||||
|
centroid varying vec3 passLighting;
|
||||||
|
centroid varying vec3 shadowDiffuseLighting;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "shadows_fragment.glsl"
|
||||||
|
#include "lighting.glsl"
|
||||||
|
|
||||||
|
float calc_coverage(float a, float alpha_ref, float falloff_rate)
|
||||||
|
{
|
||||||
|
return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
#if @normalMap
|
||||||
|
vec4 normalTex = texture2D(normalMap, normalMapUV);
|
||||||
|
|
||||||
|
vec3 normalizedNormal = normalize(passNormal);
|
||||||
|
vec3 normalizedTangent = normalize(passTangent.xyz);
|
||||||
|
vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w;
|
||||||
|
mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal);
|
||||||
|
|
||||||
|
vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (!@normalMap && @forcePPL && false)
|
||||||
|
vec3 viewNormal = gl_NormalMatrix * normalize(passNormal);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if @diffuseMap
|
||||||
|
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
|
||||||
|
#else
|
||||||
|
gl_FragData[0] = vec4(1.0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0);
|
||||||
|
|
||||||
|
float shadowing = unshadowedLightRatio(linearDepth);
|
||||||
|
if (euclideanDepth > @groundcoverFadeStart)
|
||||||
|
gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth);
|
||||||
|
|
||||||
|
vec3 lighting;
|
||||||
|
#if !PER_PIXEL_LIGHTING
|
||||||
|
lighting = passLighting + shadowDiffuseLighting * shadowing;
|
||||||
|
#else
|
||||||
|
vec3 diffuseLight, ambientLight;
|
||||||
|
doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight);
|
||||||
|
lighting = diffuseLight + ambientLight;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if @clamp
|
||||||
|
lighting = clamp(lighting, vec3(0.0), vec3(1.0));
|
||||||
|
#else
|
||||||
|
lighting = max(lighting, 0.0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gl_FragData[0].xyz *= lighting;
|
||||||
|
|
||||||
|
#if @radialFog
|
||||||
|
float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
|
||||||
|
#else
|
||||||
|
float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
|
||||||
|
#endif
|
||||||
|
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
|
||||||
|
|
||||||
|
applyShadowDebugOverlay();
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
#version 120
|
||||||
|
|
||||||
|
#define GROUNDCOVER
|
||||||
|
|
||||||
|
attribute vec4 aOffset;
|
||||||
|
attribute vec3 aRotation;
|
||||||
|
|
||||||
|
#if @diffuseMap
|
||||||
|
varying vec2 diffuseMapUV;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if @normalMap
|
||||||
|
varying vec2 normalMapUV;
|
||||||
|
varying vec4 passTangent;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting.
|
||||||
|
// They may do not look as intended with per-pixel lighting, so ignore this setting for now.
|
||||||
|
#define PER_PIXEL_LIGHTING @normalMap
|
||||||
|
|
||||||
|
varying float euclideanDepth;
|
||||||
|
varying float linearDepth;
|
||||||
|
|
||||||
|
#if PER_PIXEL_LIGHTING
|
||||||
|
varying vec3 passViewPos;
|
||||||
|
varying vec3 passNormal;
|
||||||
|
#else
|
||||||
|
centroid varying vec3 passLighting;
|
||||||
|
centroid varying vec3 shadowDiffuseLighting;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "shadows_vertex.glsl"
|
||||||
|
#include "lighting.glsl"
|
||||||
|
|
||||||
|
uniform float osg_SimulationTime;
|
||||||
|
uniform mat4 osg_ViewMatrixInverse;
|
||||||
|
uniform mat4 osg_ViewMatrix;
|
||||||
|
uniform float windSpeed;
|
||||||
|
uniform vec3 playerPos;
|
||||||
|
|
||||||
|
vec2 groundcoverDisplacement(in vec3 worldpos, float h)
|
||||||
|
{
|
||||||
|
vec2 windDirection = vec2(1.0);
|
||||||
|
vec3 footPos = playerPos;
|
||||||
|
vec3 windVec = vec3(windSpeed * windDirection, 1.0);
|
||||||
|
|
||||||
|
float v = length(windVec);
|
||||||
|
vec2 displace = vec2(2.0 * windVec + 0.1);
|
||||||
|
vec2 harmonics = vec2(0.0);
|
||||||
|
|
||||||
|
harmonics += vec2((1.0 - 0.10*v) * sin(1.0*osg_SimulationTime + worldpos.xy / 1100.0));
|
||||||
|
harmonics += vec2((1.0 - 0.04*v) * cos(2.0*osg_SimulationTime + worldpos.xy / 750.0));
|
||||||
|
harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0));
|
||||||
|
harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0));
|
||||||
|
|
||||||
|
// FIXME: stomping function does not work well in MGE:
|
||||||
|
// 1. It does not take in account Z coordinate, so it works even when player levitates.
|
||||||
|
// 2. It works more-or-less well only for grass meshes, but not for other types of plants.
|
||||||
|
// So disable this function for now, until we find a better one.
|
||||||
|
vec2 stomp = vec2(0.0);
|
||||||
|
//float d = length(worldpos.xy - footPos.xy);
|
||||||
|
//if (d < 150.0 && d > 0.0)
|
||||||
|
//{
|
||||||
|
// stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy);
|
||||||
|
//}
|
||||||
|
|
||||||
|
return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat4 rotation(in vec3 angle)
|
||||||
|
{
|
||||||
|
float sin_x = sin(angle.x);
|
||||||
|
float cos_x = cos(angle.x);
|
||||||
|
float sin_y = sin(angle.y);
|
||||||
|
float cos_y = cos(angle.y);
|
||||||
|
float sin_z = sin(angle.z);
|
||||||
|
float cos_z = cos(angle.z);
|
||||||
|
|
||||||
|
return mat4(
|
||||||
|
cos_z*cos_y+sin_x*sin_y*sin_z, -sin_z*cos_x, cos_z*sin_y+sin_z*sin_x*cos_y, 0.0,
|
||||||
|
sin_z*cos_y+cos_z*sin_x*sin_y, cos_z*cos_x, sin_z*sin_y-cos_z*sin_x*cos_y, 0.0,
|
||||||
|
-sin_y*cos_x, sin_x, cos_x*cos_y, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat3 rotation3(in mat4 rot4)
|
||||||
|
{
|
||||||
|
return mat3(
|
||||||
|
rot4[0].xyz,
|
||||||
|
rot4[1].xyz,
|
||||||
|
rot4[2].xyz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
vec3 position = aOffset.xyz;
|
||||||
|
float scale = aOffset.w;
|
||||||
|
|
||||||
|
mat4 rotation = rotation(aRotation);
|
||||||
|
vec4 displacedVertex = rotation * scale * gl_Vertex;
|
||||||
|
|
||||||
|
displacedVertex = vec4(displacedVertex.xyz + position, 1.0);
|
||||||
|
|
||||||
|
vec4 worldPos = osg_ViewMatrixInverse * gl_ModelViewMatrix * displacedVertex;
|
||||||
|
worldPos.xy += groundcoverDisplacement(worldPos.xyz, gl_Vertex.z);
|
||||||
|
vec4 viewPos = osg_ViewMatrix * worldPos;
|
||||||
|
|
||||||
|
gl_ClipVertex = viewPos;
|
||||||
|
euclideanDepth = length(viewPos.xyz);
|
||||||
|
|
||||||
|
if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd)
|
||||||
|
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
else
|
||||||
|
gl_Position = gl_ProjectionMatrix * viewPos;
|
||||||
|
|
||||||
|
linearDepth = gl_Position.z;
|
||||||
|
|
||||||
|
#if (!PER_PIXEL_LIGHTING || @shadows_enabled)
|
||||||
|
vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if @diffuseMap
|
||||||
|
diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if @normalMap
|
||||||
|
normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy;
|
||||||
|
passTangent = gl_MultiTexCoord7.xyzw * rotation;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PER_PIXEL_LIGHTING
|
||||||
|
passViewPos = viewPos.xyz;
|
||||||
|
passNormal = rotation3(rotation) * gl_Normal.xyz;
|
||||||
|
#else
|
||||||
|
vec3 diffuseLight, ambientLight;
|
||||||
|
doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting);
|
||||||
|
passLighting = diffuseLight + ambientLight;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (@shadows_enabled)
|
||||||
|
setupShadowCoords(viewPos, viewNormal);
|
||||||
|
#endif
|
||||||
|
}
|
Loading…
Reference in New Issue