terrain: use a custom drawable for multi-pass render instead of osgFX::Effect

osgFX::Effect is awkward to use because of the lazy-definition of passes, no support for compileGLObjects, useless 'Technique' abstraction and having to define silly methods like 'effectAuthor()'

Handling the multi-pass rendering inside the Drawable also avoids redundant culling tests against the same bounding box for each pass.
0.6.1
scrawl 8 years ago
parent 34130fc5cc
commit eef63a880a

@ -114,7 +114,7 @@ add_component_dir (translation
)
add_component_dir (terrain
storage world buffercache defs terraingrid material
storage world buffercache defs terraingrid material terraindrawable
)
add_component_dir (loadinglistener
@ -204,7 +204,6 @@ target_link_libraries(components
${OSGVIEWER_LIBRARIES}
${OSGTEXT_LIBRARIES}
${OSGGA_LIBRARIES}
${OSGFX_LIBRARIES}
${OSGANIMATION_LIBRARIES}
${Bullet_LIBRARIES}
${SDL2_LIBRARIES}

@ -58,11 +58,13 @@ namespace Terrain
return depth;
}
FixedFunctionTechnique::FixedFunctionTechnique(const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize)
std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector<TextureLayer> &layers, const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps, int blendmapScale, float layerTileSize)
{
std::vector<osg::ref_ptr<osg::StateSet> > passes;
bool firstLayer = true;
int i=0;
unsigned int blendmapIndex = 0;
unsigned int passIndex = 0;
for (std::vector<TextureLayer>::const_iterator it = layers.begin(); it != layers.end(); ++it)
{
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
@ -70,138 +72,91 @@ namespace Terrain
if (!firstLayer)
{
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON);
}
int texunit = 0;
if(!firstLayer)
if (useShaders)
{
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(i++);
stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap);
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
// This is to map corner vertices directly to the center of a blendmap texel.
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
stateset->addUniform(new osg::Uniform("diffuseMap", texunit));
static osg::ref_ptr<osg::TexEnvCombine> texEnvCombine;
if (!texEnvCombine)
if(!firstLayer)
{
texEnvCombine = new osg::TexEnvCombine;
texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE);
texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
}
stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON);
++texunit;
}
// Add the actual layer texture multiplied by the alpha map.
osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap;
stateset->setTextureAttributeAndModes(texunit, tex.get());
stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
++texunit;
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(blendmapIndex++);
firstLayer = false;
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
addPass(stateset);
}
}
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
stateset->addUniform(new osg::Uniform("blendMap", texunit));
}
ShaderTechnique::ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize)
{
bool firstLayer = true;
int i=0;
for (std::vector<TextureLayer>::const_iterator it = layers.begin(); it != layers.end(); ++it)
{
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
if (it->mNormalMap)
{
++texunit;
stateset->setTextureAttributeAndModes(texunit, it->mNormalMap);
stateset->addUniform(new osg::Uniform("normalMap", texunit));
}
if (!firstLayer)
{
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON);
Shader::ShaderManager::DefineMap defineMap;
defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0";
defineMap["clamp"] = clampLighting ? "1" : "0";
defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0";
defineMap["blendMap"] = !firstLayer ? "1" : "0";
defineMap["colorMode"] = "2";
defineMap["specularMap"] = it->mSpecular ? "1" : "0";
defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0";
osg::ref_ptr<osg::Shader> vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT);
if (!vertexShader || !fragmentShader)
throw std::runtime_error("Unable to create shader");
stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader));
}
else
{
if(!firstLayer)
{
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(blendmapIndex++);
int texunit = 0;
stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap);
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
// This is to map corner vertices directly to the center of a blendmap texel.
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
stateset->addUniform(new osg::Uniform("diffuseMap", texunit));
static osg::ref_ptr<osg::TexEnvCombine> texEnvCombine;
if (!texEnvCombine)
{
texEnvCombine = new osg::TexEnvCombine;
texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE);
texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
}
if(!firstLayer)
{
++texunit;
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(i++);
stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
++texunit;
}
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
stateset->addUniform(new osg::Uniform("blendMap", texunit));
}
// Add the actual layer texture multiplied by the alpha map.
osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap;
stateset->setTextureAttributeAndModes(texunit, tex.get());
if (it->mNormalMap)
{
++texunit;
stateset->setTextureAttributeAndModes(texunit, it->mNormalMap);
stateset->addUniform(new osg::Uniform("normalMap", texunit));
stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
}
Shader::ShaderManager::DefineMap defineMap;
defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0";
defineMap["clamp"] = clampLighting ? "1" : "0";
defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0";
defineMap["blendMap"] = !firstLayer ? "1" : "0";
defineMap["colorMode"] = "2";
defineMap["specularMap"] = it->mSpecular ? "1" : "0";
defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0";
osg::ref_ptr<osg::Shader> vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT);
if (!vertexShader || !fragmentShader)
throw std::runtime_error("Unable to create shader");
stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader));
firstLayer = false;
addPass(stateset);
}
}
Effect::Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector<TextureLayer> &layers, const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps,
int blendmapScale, float layerTileSize)
: mShaderManager(shaderManager)
, mUseShaders(useShaders)
, mForcePerPixelLighting(forcePerPixelLighting)
, mClampLighting(clampLighting)
, mLayers(layers)
, mBlendmaps(blendmaps)
, mBlendmapScale(blendmapScale)
, mLayerTileSize(layerTileSize)
{
selectTechnique(0);
}
stateset->setRenderBinDetails(passIndex++, "RenderBin");
bool Effect::define_techniques()
{
try
{
if (mUseShaders && mShaderManager)
addTechnique(new ShaderTechnique(*mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
else
addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
passes.push_back(stateset);
}
catch (std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
}
return true;
return passes;
}
}

@ -1,8 +1,7 @@
#ifndef COMPONENTS_TERRAIN_MATERIAL_H
#define COMPONENTS_TERRAIN_MATERIAL_H
#include <osgFX/Technique>
#include <osgFX/Effect>
#include <osg/StateSet>
#include "defs.hpp"
@ -27,61 +26,9 @@ namespace Terrain
bool mSpecular;
};
class FixedFunctionTechnique : public osgFX::Technique
{
public:
FixedFunctionTechnique(
const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
protected:
virtual void define_passes() {}
};
class ShaderTechnique : public osgFX::Technique
{
public:
ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting,
const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
protected:
virtual void define_passes() {}
};
class Effect : public osgFX::Effect
{
public:
Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager,
const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
virtual bool define_techniques();
virtual const char *effectName() const
{
return NULL;
}
virtual const char *effectDescription() const
{
return NULL;
}
virtual const char *effectAuthor() const
{
return NULL;
}
private:
Shader::ShaderManager* mShaderManager;
bool mUseShaders;
bool mForcePerPixelLighting;
bool mClampLighting;
std::vector<TextureLayer> mLayers;
std::vector<osg::ref_ptr<osg::Texture2D> > mBlendmaps;
int mBlendmapScale;
float mLayerTileSize;
};
std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager,
const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
}
#endif

@ -0,0 +1,85 @@
#include "terraindrawable.hpp"
#include <osgUtil/CullVisitor>
#include <components/sceneutil/lightmanager.hpp>
namespace Terrain
{
TerrainDrawable::TerrainDrawable()
{
mLightListCallback = new SceneUtil::LightListCallback;
}
TerrainDrawable::TerrainDrawable(const TerrainDrawable &copy, const osg::CopyOp &copyop)
: osg::Geometry(copy, copyop)
, mPasses(copy.mPasses)
, mLightListCallback(copy.mLightListCallback)
{
}
void TerrainDrawable::accept(osg::NodeVisitor &nv)
{
if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR)
{
osg::Geometry::accept(nv);
}
else if (nv.validNodeMask(*this))
{
nv.pushOntoNodePath(this);
cull(static_cast<osgUtil::CullVisitor*>(&nv));
nv.popFromNodePath();
}
}
inline float distance(const osg::Vec3& coord,const osg::Matrix& matrix)
{
return -((float)coord[0]*(float)matrix(0,2)+(float)coord[1]*(float)matrix(1,2)+(float)coord[2]*(float)matrix(2,2)+matrix(3,2));
}
void TerrainDrawable::cull(osgUtil::CullVisitor *cv)
{
const osg::BoundingBox& bb = getBoundingBox();
if (cv->isCulled(getBoundingBox()))
return;
osg::RefMatrix& matrix = *cv->getModelViewMatrix();
float depth = bb.valid() ? distance(bb.center(),matrix) : 0.0f;
if (osg::isNaN(depth))
return;
bool pushedLight = mLightListCallback->pushLightState(this, cv);
for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it)
{
cv->pushStateSet(*it);
cv->addDrawableAndDepth(this, &matrix, depth);
cv->popStateSet();
}
if (pushedLight)
cv->popStateSet();
}
void TerrainDrawable::setPasses(const TerrainDrawable::PassVector &passes)
{
mPasses = passes;
}
void TerrainDrawable::compileGLObjects(osg::RenderInfo &renderInfo) const
{
for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it)
{
osg::StateSet* stateset = *it;
stateset->compileGLObjects(*renderInfo.getState());
}
osg::Geometry::compileGLObjects(renderInfo);
}
}

@ -0,0 +1,51 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H
#define OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H
#include <osg/Geometry>
namespace osgUtil
{
class CullVisitor;
}
namespace SceneUtil
{
class LightListCallback;
}
namespace Terrain
{
/**
* Subclass of Geometry that supports built in multi-pass rendering and built in LightListCallback.
*/
class TerrainDrawable : public osg::Geometry
{
public:
virtual osg::Object* cloneType() const { return new TerrainDrawable (); }
virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new TerrainDrawable (*this,copyop); }
virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const TerrainDrawable *>(obj)!=NULL; }
virtual const char* className() const { return "TerrainDrawable"; }
virtual const char* libraryName() const { return "Terrain"; }
TerrainDrawable();
TerrainDrawable(const TerrainDrawable& copy, const osg::CopyOp& copyop);
virtual void accept(osg::NodeVisitor &nv);
void cull(osgUtil::CullVisitor* cv);
typedef std::vector<osg::ref_ptr<osg::StateSet> > PassVector;
void setPasses (const PassVector& passes);
virtual void compileGLObjects(osg::RenderInfo& renderInfo) const;
private:
PassVector mPasses;
osg::ref_ptr<SceneUtil::LightListCallback> mLightListCallback;
};
}
#endif

@ -3,6 +3,9 @@
#include <memory>
#include <osg/Material>
#include <osg/Geometry>
#include <osgUtil/IncrementalCompileOperation>
#include <OpenThreads/ScopedLock>
@ -16,15 +19,10 @@
#include <components/esm/loadland.hpp>
#include <osg/Geometry>
#include <osg/KdTree>
#include <osgFX/Effect>
#include <osgUtil/IncrementalCompileOperation>
#include "material.hpp"
#include "storage.hpp"
#include "terraindrawable.hpp"
namespace
{
@ -124,7 +122,7 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
mStorage->fillVertexBuffers(0, chunkSize, chunkCenter, positions, normals, colors);
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
osg::ref_ptr<TerrainDrawable> geometry (new TerrainDrawable);
geometry->setVertexArray(positions);
geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
@ -147,10 +145,6 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
std::vector<osg::ref_ptr<osg::Image> > blendmaps;
mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList);
// For compiling textures, I don't think the osgFX::Effect does it correctly
osg::ref_ptr<osg::Node> textureCompileDummy (new osg::Node);
unsigned int dummyTextureCounter = 0;
bool useShaders = mResourceSystem->getSceneManager()->getForceShaders();
if (!mResourceSystem->getSceneManager()->getClampLighting())
useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps
@ -172,7 +166,6 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
mTextureCache[it->mDiffuseMap] = texture;
}
textureLayer.mDiffuseMap = texture;
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture);
if (!it->mNormalMap.empty())
{
@ -185,7 +178,6 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
mResourceSystem->getSceneManager()->applyFilterSettings(texture);
mTextureCache[it->mNormalMap] = texture;
}
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture);
textureLayer.mNormalMap = texture;
}
@ -205,8 +197,6 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
texture->setResizeNonPowerOfTwoHint(false);
blendmapTextures.push_back(texture);
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back());
}
// use texture coordinates for both texture units, the layer texture and blend texture
@ -214,21 +204,15 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
geometry->setTexCoordArray(i, mCache.getUVBuffer());
float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize;
osg::ref_ptr<osgFX::Effect> effect (new Terrain::Effect(mShaderManager ? useShaders : false, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(),
mShaderManager, layers, blendmapTextures, blendmapScale, blendmapScale));
effect->addCullCallback(new SceneUtil::LightListCallback);
transform->addChild(effect);
osg::Node* toAttach = geometry.get();
geometry->setPasses(createPasses(mShaderManager ? useShaders : false, mResourceSystem->getSceneManager()->getForcePerPixelLighting(),
mResourceSystem->getSceneManager()->getClampLighting(), mShaderManager, layers, blendmapTextures, blendmapScale, blendmapScale));
effect->addChild(toAttach);
transform->addChild(geometry);
if (mIncrementalCompileOperation)
{
mIncrementalCompileOperation->add(toAttach);
mIncrementalCompileOperation->add(textureCompileDummy);
mIncrementalCompileOperation->add(geometry);
}
return transform;

Loading…
Cancel
Save