From 6000e48bba87eb22e8f4b5b29340e7f74c07d6fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 19 Feb 2016 01:30:15 +0100 Subject: [PATCH] Add terrain shaders and normal map support Textures with _n filename suffix are automatically recognized as terrain normal maps. --- components/resource/scenemanager.cpp | 20 +++++ components/resource/scenemanager.hpp | 5 ++ components/terrain/material.cpp | 106 +++++++++++++++++++++++++-- components/terrain/material.hpp | 34 ++++++++- components/terrain/terraingrid.cpp | 31 +++++++- files/shaders/CMakeLists.txt | 2 + files/shaders/terrain_fragment.glsl | 64 ++++++++++++++++ files/shaders/terrain_vertex.glsl | 36 +++++++++ 8 files changed, 283 insertions(+), 15 deletions(-) create mode 100644 files/shaders/terrain_fragment.glsl create mode 100644 files/shaders/terrain_vertex.glsl diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 172a6511a..57187c734 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -228,6 +228,11 @@ namespace Resource mForceShaders = force; } + bool SceneManager::getForceShaders() const + { + return mForceShaders; + } + void SceneManager::recreateShaders(osg::ref_ptr node) { Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl"); @@ -243,16 +248,31 @@ namespace Resource mClampLighting = clamp; } + bool SceneManager::getClampLighting() const + { + return mClampLighting; + } + void SceneManager::setForcePerPixelLighting(bool force) { mForcePerPixelLighting = force; } + bool SceneManager::getForcePerPixelLighting() const + { + return mForcePerPixelLighting; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types } + Shader::ShaderManager &SceneManager::getShaderManager() + { + return *mShaderManager.get(); + } + void SceneManager::setShaderPath(const std::string &path) { mShaderManager->setShaderPath(path); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index db3de3f48..8f2853789 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -40,17 +40,22 @@ namespace Resource SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); + Shader::ShaderManager& getShaderManager(); + /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. void recreateShaders(osg::ref_ptr node); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); + bool getForceShaders() const; /// @see ShaderVisitor::setClampLighting void setClampLighting(bool clamp); + bool getClampLighting() const; /// @see ShaderVisitor::setForcePerPixelLighting void setForcePerPixelLighting(bool force); + bool getForcePerPixelLighting() const; void setShaderPath(const std::string& path); diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 59d06f254..fe32fe543 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,21 +1,26 @@ #include "material.hpp" +#include +#include + #include #include #include #include #include -#include + +#include + namespace Terrain { - FixedFunctionTechnique::FixedFunctionTechnique(const std::vector >& layers, + FixedFunctionTechnique::FixedFunctionTechnique(const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize) { bool firstLayer = true; int i=0; - for (std::vector >::const_iterator it = layers.begin(); it != layers.end(); ++it) + for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) { osg::ref_ptr stateset (new osg::StateSet); @@ -53,7 +58,7 @@ namespace Terrain } // Add the actual layer texture multiplied by the alpha map. - osg::ref_ptr tex = *it; + osg::ref_ptr tex = it->mDiffuseMap; stateset->setTextureAttributeAndModes(texunit, tex.get()); osg::ref_ptr texMat (new osg::TexMat); @@ -66,9 +71,85 @@ namespace Terrain } } - Effect::Effect(const std::vector > &layers, const std::vector > &blendmaps, + ShaderTechnique::ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, const std::vector& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize) + { + bool firstLayer = true; + int i=0; + for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) + { + osg::ref_ptr stateset (new osg::StateSet); + + if (!firstLayer) + { + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + osg::ref_ptr depth (new osg::Depth); + depth->setFunction(osg::Depth::EQUAL); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + + int texunit = 0; + + stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); + + osg::ref_ptr texMat (new osg::TexMat); + texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(layerTileSize,layerTileSize,1.f))); + stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); + + if(!firstLayer) + { + ++texunit; + osg::ref_ptr blendmap = blendmaps.at(i++); + + stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + + // This is to map corner vertices directly to the center of a blendmap texel. + osg::Matrixf texMat; + float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); + texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); + texMat.preMultScale(osg::Vec3f(scale, scale, 1.f)); + texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); + + stateset->setTextureAttributeAndModes(texunit, new osg::TexMat(texMat)); + stateset->addUniform(new osg::Uniform("blendMap", texunit)); + } + + if (it->mNormalMap) + { + ++texunit; + stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); + stateset->addUniform(new osg::Uniform("normalMap", texunit)); + } + + 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"; + + osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); + osg::ref_ptr 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 &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) - : mLayers(layers) + : mShaderManager(shaderManager) + , mUseShaders(useShaders) + , mForcePerPixelLighting(forcePerPixelLighting) + , mClampLighting(clampLighting) + , mLayers(layers) , mBlendmaps(blendmaps) , mBlendmapScale(blendmapScale) , mLayerTileSize(layerTileSize) @@ -82,7 +163,18 @@ namespace Terrain bool Effect::define_techniques() { - addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + try + { + if (mUseShaders) + addTechnique(new ShaderTechnique(mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + else + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + } + catch (std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + } return true; } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index dd00e41ed..65349a5e7 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -11,14 +11,36 @@ namespace osg class Texture2D; } +namespace Shader +{ + class ShaderManager; +} + namespace Terrain { + struct TextureLayer + { + osg::ref_ptr mDiffuseMap; + osg::ref_ptr mNormalMap; // optional + }; + class FixedFunctionTechnique : public osgFX::Technique { public: FixedFunctionTechnique( - const std::vector >& layers, + const std::vector& layers, + const std::vector >& 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& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); protected: @@ -28,8 +50,8 @@ namespace Terrain class Effect : public osgFX::Effect { public: - Effect( - const std::vector >& layers, + Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager& shaderManager, + const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); virtual bool define_techniques(); @@ -48,7 +70,11 @@ namespace Terrain } private: - std::vector > mLayers; + Shader::ShaderManager& mShaderManager; + bool mUseShaders; + bool mForcePerPixelLighting; + bool mClampLighting; + std::vector mLayers; std::vector > mBlendmaps; int mBlendmapScale; float mLayerTileSize; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index fefb0b8bc..ddbb2fe23 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -148,11 +148,15 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu osg::ref_ptr textureCompileDummy (new osg::Node); unsigned int dummyTextureCounter = 0; - std::vector > layerTextures; + 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 + std::vector layers; { OpenThreads::ScopedLock lock(mTextureCacheMutex); for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { + TextureLayer textureLayer; osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; if (!texture) { @@ -162,8 +166,26 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu mResourceSystem->getSceneManager()->applyFilterSettings(texture); mTextureCache[it->mDiffuseMap] = texture; } - layerTextures.push_back(texture); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); + textureLayer.mDiffuseMap = texture; + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); + + if (!it->mNormalMap.empty()) + { + texture = mTextureCache[it->mNormalMap]; + if (!texture) + { + texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mNormalMap)); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); + mTextureCache[it->mNormalMap] = texture; + } + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); + textureLayer.mNormalMap = texture; + useShaders = true; + } + + layers.push_back(textureLayer); } } @@ -185,7 +207,8 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu geometry->setTexCoordArray(i, mCache.getUVBuffer()); float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; - osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, blendmapScale, blendmapScale)); + osg::ref_ptr effect (new Terrain::Effect(useShaders, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(), + mResourceSystem->getSceneManager()->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale)); effect->addCullCallback(new SceneUtil::LightListCallback); diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index a7b631139..31db4a3eb 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -8,6 +8,8 @@ set(SHADER_FILES water_nm.png objects_vertex.glsl objects_fragment.glsl + terrain_vertex.glsl + terrain_fragment.glsl lighting.glsl ) diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl new file mode 100644 index 000000000..3bfd16b0e --- /dev/null +++ b/files/shaders/terrain_fragment.glsl @@ -0,0 +1,64 @@ +#version 120 + +varying vec2 uv; + +uniform sampler2D diffuseMap; + +#if @normalMap +uniform sampler2D normalMap; +#endif + +#if @blendMap +uniform sampler2D blendMap; +#endif + +varying float depth; + +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +varying vec4 lighting; +#else +varying vec3 passViewPos; +varying vec3 passViewNormal; +varying vec4 passColor; +#endif + +#include "lighting.glsl" + +void main() +{ + vec2 diffuseMapUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; + + gl_FragData[0] = vec4(texture2D(diffuseMap, diffuseMapUV).xyz, 1.0); + +#if @blendMap + vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; + gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; +#endif + +#if PER_PIXEL_LIGHTING + vec3 viewNormal = passViewNormal; +#endif + +#if @normalMap + vec3 normalTex = texture2D(normalMap, diffuseMapUV).xyz; + + vec3 viewTangent = (gl_ModelViewMatrix * vec4(1.0, 0.0, 0.0, 0.0)).xyz; + vec3 viewBinormal = normalize(cross(viewTangent, viewNormal)); + viewTangent = normalize(cross(viewNormal, viewBinormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + mat3 tbn = mat3(viewTangent, viewBinormal, viewNormal); + + viewNormal = normalize(tbn * (normalTex * 2.0 - 1.0)); +#endif + + +#if !PER_PIXEL_LIGHTING + gl_FragData[0] *= lighting; +#else + gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor); +#endif + + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +} diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl new file mode 100644 index 000000000..43cb5d31b --- /dev/null +++ b/files/shaders/terrain_vertex.glsl @@ -0,0 +1,36 @@ +#version 120 + +varying vec2 uv; +varying float depth; + +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +varying vec4 lighting; +#else +varying vec3 passViewPos; +varying vec3 passViewNormal; +varying vec4 passColor; +#endif + +#include "lighting.glsl" + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + depth = gl_Position.z; + + vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; + vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + +#if !PER_PIXEL_LIGHTING + lighting = doLighting(viewPos.xyz, viewNormal, gl_Color); +#else + passViewPos = viewPos.xyz; + passViewNormal = viewNormal; + passColor = gl_Color; +#endif + + uv = gl_MultiTexCoord0.xy; +}