diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d42f043e6..ee40f9823 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -85,6 +85,7 @@ IF(DYNAMIC_OPENSCENEGRAPH) ADD_SUBDIRECTORY(osgscribe) ADD_SUBDIRECTORY(osgsequence) ADD_SUBDIRECTORY(osgshaders) + ADD_SUBDIRECTORY(osgshadergen) ADD_SUBDIRECTORY(osgshaderterrain) ADD_SUBDIRECTORY(osgshadow) ADD_SUBDIRECTORY(osgshape) diff --git a/examples/osgshadergen/CMakeLists.txt b/examples/osgshadergen/CMakeLists.txt new file mode 100644 index 000000000..d58a4dc3b --- /dev/null +++ b/examples/osgshadergen/CMakeLists.txt @@ -0,0 +1,6 @@ +#this file is automatically generated + + +SET(TARGET_SRC osgshadergen.cpp ) +#### end var setup ### +SETUP_EXAMPLE(osgshadergen) diff --git a/examples/osgshadergen/osgshadergen.cpp b/examples/osgshadergen/osgshadergen.cpp new file mode 100644 index 000000000..aa8520730 --- /dev/null +++ b/examples/osgshadergen/osgshadergen.cpp @@ -0,0 +1,167 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This application is open source and may be redistributed and/or modified + * freely and without restriction, both in commericial and non commericial applications, + * as long as this copyright notice is maintained. + * + * This application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +class ShaderGenReadFileCallback : public osgDB::Registry::ReadFileCallback +{ +public: + ShaderGenReadFileCallback() + { + } + + virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& filename, const osgDB::ReaderWriter::Options* options) + { + osgDB::ReaderWriter::ReadResult result = osgDB::Registry::ReadFileCallback::readNode(filename, options); + if (osg::Node *node = result.getNode()) + { + _visitor.reset(); + node->accept(_visitor); + } + return result; + } + + void setRootStateSet(osg::StateSet *stateSet) { _visitor.setRootStateSet(stateSet); } + osg::StateSet *getRootStateSet() const { return _visitor.getRootStateSet(); } + +protected: + osgUtil::ShaderGenVisitor _visitor; +}; + + +int main(int argc, char** argv) +{ + // use an ArgumentParser object to manage the program arguments. + osg::ArgumentParser arguments(&argc,argv); + + arguments.getApplicationUsage()->setApplicationName(arguments.getApplicationName()); + arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+ + " is an example of conversion of fixed function pipeline to GLSL"); + arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ..."); + + osgViewer::Viewer viewer(arguments); + + unsigned int helpType = 0; + if ((helpType = arguments.readHelpType())) + { + arguments.getApplicationUsage()->write(std::cout, helpType); + return 1; + } + + // report any errors if they have occurred when parsing the program arguments. + if (arguments.errors()) + { + arguments.writeErrorMessages(std::cout); + return 1; + } + + if (arguments.argc()<=1) + { + arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION); + return 1; + } + + // set up the camera manipulators. + { + osg::ref_ptr keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator; + + keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() ); + keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() ); + keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() ); + keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() ); + + std::string pathfile; + char keyForAnimationPath = '5'; + while (arguments.read("-p",pathfile)) + { + osgGA::AnimationPathManipulator* apm = new osgGA::AnimationPathManipulator(pathfile); + if (apm || !apm->valid()) + { + unsigned int num = keyswitchManipulator->getNumMatrixManipulators(); + keyswitchManipulator->addMatrixManipulator( keyForAnimationPath, "Path", apm ); + keyswitchManipulator->selectMatrixManipulator(num); + ++keyForAnimationPath; + } + } + + viewer.setCameraManipulator( keyswitchManipulator.get() ); + } + + // add the state manipulator + viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) ); + + // add the thread model handler + viewer.addEventHandler(new osgViewer::ThreadingHandler); + + // add the window size toggle handler + viewer.addEventHandler(new osgViewer::WindowSizeHandler); + + // add the stats handler + viewer.addEventHandler(new osgViewer::StatsHandler); + + // add the help handler + viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage())); + + // add the record camera path handler + viewer.addEventHandler(new osgViewer::RecordCameraPathHandler); + + // add the LOD Scale handler + viewer.addEventHandler(new osgViewer::LODScaleHandler); + + // add the screen capture handler + viewer.addEventHandler(new osgViewer::ScreenCaptureHandler); + + // Register shader generator callback + ShaderGenReadFileCallback *readFileCallback = new ShaderGenReadFileCallback; + // All read nodes will inherit root state set. + readFileCallback->setRootStateSet(viewer.getCamera()->getStateSet()); + osgDB::Registry::instance()->setReadFileCallback(readFileCallback); + + // load the data + osg::ref_ptr loadedModel = osgDB::readNodeFiles(arguments); + if (!loadedModel) + { + std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl; + return 1; + } + + // any option left unread are converted into errors to write out later. + arguments.reportRemainingOptionsAsUnrecognized(); + + // report any errors if they have occurred when parsing the program arguments. + if (arguments.errors()) + { + arguments.writeErrorMessages(std::cout); + return 1; + } + + viewer.setSceneData( loadedModel.get() ); + + viewer.realize(); + + return viewer.run(); + +} diff --git a/include/osgUtil/ShaderGen b/include/osgUtil/ShaderGen new file mode 100644 index 000000000..7cf4edd2d --- /dev/null +++ b/include/osgUtil/ShaderGen @@ -0,0 +1,84 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2009 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +/** + * \brief Shader generator framework. + * \author Maciej Krol + */ + +#ifndef OSGUTIL_SHADER_STATE_ +#define OSGUTIL_SHADER_STATE_ 1 + +#include +#include +#include + +namespace osgUtil +{ + +class OSGUTIL_EXPORT ShaderGenCache : public osg::Referenced +{ +public: + enum StateMask + { + BLEND = 1, + LIGHTING = 2, + FOG = 4, + DIFFUSE_MAP = 8, //< Texture in unit 0 + NORMAL_MAP = 16 //< Texture in unit 1 and vertex attribute array 6 + }; + + typedef std::map > StateSetMap; + + ShaderGenCache() {}; + + void setStateSet(unsigned int stateMask, osg::StateSet *program); + osg::StateSet *getStateSet(unsigned int stateMask) const; + osg::StateSet *getOrCreateStateSet(unsigned int stateMask); + +protected: + osg::StateSet *createStateSet(unsigned int stateMask) const; + mutable OpenThreads::Mutex _mutex; + StateSetMap _stateSetMap; + +}; + +class OSGUTIL_EXPORT ShaderGenVisitor : public osg::NodeVisitor +{ +public: + ShaderGenVisitor(); + ShaderGenVisitor(ShaderGenCache *stateCache); + + void setStateCache(ShaderGenCache *stateCache) { _stateCache = stateCache; } + ShaderGenCache *getStateCache() const { return _stateCache.get(); } + + /// Top level state set applied as the first one. + void setRootStateSet(osg::StateSet *stateSet); + osg::StateSet *getRootStateSet() const { return _rootStateSet.get(); } + + void apply(osg::Node &node); + void apply(osg::Geode &geode); + + void reset(); + +protected: + void update(osg::Drawable *drawable); + + osg::ref_ptr _stateCache; + osg::ref_ptr _state; + osg::ref_ptr _rootStateSet; +}; + +} + +#endif diff --git a/src/osgUtil/CMakeLists.txt b/src/osgUtil/CMakeLists.txt index dc57bbcc8..bfa074bc3 100644 --- a/src/osgUtil/CMakeLists.txt +++ b/src/osgUtil/CMakeLists.txt @@ -37,6 +37,7 @@ SET(LIB_PUBLIC_HEADERS ${HEADER_PATH}/ReversePrimitiveFunctor ${HEADER_PATH}/SceneView ${HEADER_PATH}/SceneGraphBuilder + ${HEADER_PATH}/ShaderGen ${HEADER_PATH}/Simplifier ${HEADER_PATH}/SmoothingVisitor ${HEADER_PATH}/StateGraph @@ -76,6 +77,7 @@ ADD_LIBRARY(${LIB_NAME} RenderStage.cpp ReversePrimitiveFunctor.cpp SceneView.cpp + ShaderGen.cpp Simplifier.cpp SmoothingVisitor.cpp SceneGraphBuilder.cpp diff --git a/src/osgUtil/ShaderGen.cpp b/src/osgUtil/ShaderGen.cpp new file mode 100644 index 000000000..c09c233b6 --- /dev/null +++ b/src/osgUtil/ShaderGen.cpp @@ -0,0 +1,383 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2009 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +/** + * \brief Shader generator framework. + * \author Maciej Krol + */ + +#include +#include +#include // for ShaderGenVisitor::update +#include + +using namespace osgUtil; + +namespace osgUtil +{ + +/// State extended by mode/attribute accessors +class StateEx : public osg::State +{ +public: + StateEx() : State() {} + + osg::StateAttribute::GLModeValue getMode(osg::StateAttribute::GLMode mode, + osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const + { + return getMode(_modeMap, mode, def); + } + + osg::StateAttribute *getAttribute(osg::StateAttribute::Type type, unsigned int member = 0) const + { + return getAttribute(_attributeMap, type, member); + } + + osg::StateAttribute::GLModeValue getTextureMode(unsigned int unit, + osg::StateAttribute::GLMode mode, + osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const + { + return unit < _textureModeMapList.size() ? getMode(_textureModeMapList[unit], mode, def) : def; + } + + osg::StateAttribute *getTextureAttribute(unsigned int unit, osg::StateAttribute::Type type) const + { + return unit < _textureAttributeMapList.size() ? getAttribute(_textureAttributeMapList[unit], type, 0) : 0; + } + + osg::Uniform *getUniform(const std::string& name) const + { + UniformMap::const_iterator it = _uniformMap.find(name); + return it != _uniformMap.end() ? + const_cast(it->second.uniformVec.back().first) : 0; + } + +protected: + + osg::StateAttribute::GLModeValue getMode(const ModeMap &modeMap, + osg::StateAttribute::GLMode mode, + osg::StateAttribute::GLModeValue def = osg::StateAttribute::INHERIT) const + { + ModeMap::const_iterator it = modeMap.find(mode); + return (it != modeMap.end() && it->second.valueVec.size()) ? it->second.valueVec.back() : def; + } + + osg::StateAttribute *getAttribute(const AttributeMap &attributeMap, + osg::StateAttribute::Type type, unsigned int member = 0) const + { + AttributeMap::const_iterator it = attributeMap.find(std::make_pair(type, member)); + return (it != attributeMap.end() && it->second.attributeVec.size()) ? + const_cast(it->second.attributeVec.back().first) : 0; + } +}; + +} + +void ShaderGenCache::setStateSet(unsigned int stateMask, osg::StateSet *stateSet) +{ + OpenThreads::ScopedLock lock(_mutex); + _stateSetMap[stateMask] = stateSet; +} + +osg::StateSet *ShaderGenCache::getStateSet(unsigned int stateMask) const +{ + OpenThreads::ScopedLock lock(_mutex); + StateSetMap::const_iterator it = _stateSetMap.find(stateMask); + return (it != _stateSetMap.end()) ? it->second.get() : 0; +} + +osg::StateSet *ShaderGenCache::getOrCreateStateSet(unsigned int stateMask) +{ + OpenThreads::ScopedLock lock(_mutex); + StateSetMap::iterator it = _stateSetMap.find(stateMask); + if (it == _stateSetMap.end()) + { + osg::StateSet *stateSet = createStateSet(stateMask); + _stateSetMap.insert(it, std::make_pair(stateMask, stateSet)); + return stateSet; + } + return it->second.get(); +} + +osg::StateSet *ShaderGenCache::createStateSet(unsigned int stateMask) const +{ + osg::StateSet *stateSet = new osg::StateSet; + osg::Program *program = new osg::Program; + stateSet->setAttribute(program); + + std::ostringstream vert; + std::ostringstream frag; + + // write varyings + if ((stateMask & LIGHTING) && !(stateMask & NORMAL_MAP)) + { + vert << "varying vec3 normalDir;\n"; + } + + if (stateMask & (LIGHTING | NORMAL_MAP)) + { + vert << "varying vec3 lightDir;\n"; + } + + if (stateMask & (LIGHTING | NORMAL_MAP | FOG)) + { + vert << "varying vec3 viewDir;\n"; + } + + // copy varying to fragment shader + frag << vert.str(); + + // write uniforms and attributes + int unit = 0; + if (stateMask & DIFFUSE_MAP) + { + osg::Uniform *diffuseMap = new osg::Uniform("diffuseMap", unit++); + stateSet->addUniform(diffuseMap); + frag << "uniform sampler2D diffuseMap;\n"; + } + + if (stateMask & NORMAL_MAP) + { + osg::Uniform *normalMap = new osg::Uniform("normalMap", unit++); + stateSet->addUniform(normalMap); + frag << "uniform sampler2D normalMap;\n"; + program->addBindAttribLocation("tangent", 6); + vert << "attribute vec3 tangent;\n"; + } + + vert << "\n"\ + "void main()\n"\ + "{\n"\ + " gl_Position = ftransform();\n"; + + if (stateMask & (DIFFUSE_MAP | NORMAL_MAP)) + { + vert << " gl_TexCoord[0] = gl_MultiTexCoord0;\n"; + } + + if (stateMask & NORMAL_MAP) + { + vert << + " vec3 n = gl_NormalMatrix * gl_Normal;\n"\ + " vec3 t = gl_NormalMatrix * tangent;\n"\ + " vec3 b = cross(n, t);\n"\ + " vec3 dir = -vec3(gl_ModelViewMatrix * gl_Vertex);\n"\ + " viewDir.x = dot(dir, t);\n"\ + " viewDir.y = dot(dir, b);\n"\ + " viewDir.z = dot(dir, n);\n"\ + " vec4 lpos = gl_LightSource[0].position;\n"\ + " if (lpos.w == 0.0)\n"\ + " dir = lpos.xyz;\n"\ + " else\n"\ + " dir += lpos.xyz;\n"\ + " lightDir.x = dot(dir, t);\n"\ + " lightDir.y = dot(dir, b);\n"\ + " lightDir.z = dot(dir, n);\n"; + } + else if (stateMask & LIGHTING) + { + vert << + " normalDir = gl_NormalMatrix * gl_Normal;\n"\ + " vec3 dir = -vec3(gl_ModelViewMatrix * gl_Vertex);\n"\ + " viewDir = dir;\n"\ + " vec4 lpos = gl_LightSource[0].position;\n"\ + " if (lpos.w == 0.0)\n"\ + " lightDir = lpos.xyz;\n"\ + " else\n"\ + " lightDir = lpos.xyz + dir;\n"; + } + else if (stateMask & FOG) + { + vert << + " viewDir = -vec3(gl_ModelViewMatrix * gl_Vertex);\n"\ + " gl_FrontColor = gl_Color;\n"; + } + else + { + vert << " gl_FrontColor = gl_Color;\n"; + } + + vert << "}\n"; + + frag << "\n"\ + "void main()\n"\ + "{\n"; + + if (stateMask & DIFFUSE_MAP) + { + frag << " vec4 base = texture2D(diffuseMap, gl_TexCoord[0].st);\n"; + } + else + { + frag << " vec4 base = vec4(1.0);\n"; + } + + if (stateMask & NORMAL_MAP) + { + frag << " vec3 normalDir = texture2D(normalMap, gl_TexCoord[0].st).xyz*2.0-1.0;\n"; + } + + if (stateMask & (LIGHTING | NORMAL_MAP)) + { + frag << + " vec3 nd = normalize(normalDir);\n"\ + " vec3 ld = normalize(lightDir);\n"\ + " vec3 vd = normalize(viewDir);\n"\ + " vec4 color = gl_FrontLightModelProduct.sceneColor;\n"\ + " color += gl_FrontLightProduct[0].ambient;\n"\ + " float diff = max(dot(ld, nd), 0.0);\n"\ + " color += gl_FrontLightProduct[0].diffuse * diff;\n"\ + " color *= base;\n"\ + " if (diff > 0.0)\n"\ + " {\n"\ + " vec3 halfDir = normalize(ld+vd);\n"\ + " color.rgb += base.a * gl_FrontLightProduct[0].specular.rgb * \n"\ + " pow(max(dot(halfDir, nd), 0.0), gl_FrontMaterial.shininess);\n"\ + " }\n"; + } + else + { + frag << " vec4 color = base;\n"; + } + + if (!(stateMask & LIGHTING)) + { + frag << " color *= gl_Color;\n"; + } + + if (stateMask & FOG) + { + frag << + " float d2 = dot(viewDir, viewDir);//gl_FragCoord.z/gl_FragCoord.w;\n"\ + " float f = exp2(-1.442695*gl_Fog.density*gl_Fog.density*d2);\n"\ + " color.rgb = mix(gl_Fog.color.rgb, color.rgb, clamp(f, 0.0, 1.0));\n"; + } + + frag << " gl_FragColor = color;\n"; + frag << "}\n"; + + std::string vertstr = vert.str(); + std::string fragstr = frag.str(); + + osg::notify(osg::DEBUG_INFO) << "ShaderGenCache Vertex shader:\n" << vertstr << std::endl; + osg::notify(osg::DEBUG_INFO) << "ShaderGenCache Fragment shader:\n" << fragstr << std::endl; + + program->addShader(new osg::Shader(osg::Shader::VERTEX, vertstr)); + program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragstr)); + + return stateSet; +} + +ShaderGenVisitor::ShaderGenVisitor() : + NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _stateCache(new ShaderGenCache), + _state(new StateEx) +{ +} + +ShaderGenVisitor::ShaderGenVisitor(ShaderGenCache *stateCache) : + NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _stateCache(stateCache), + _state(new StateEx) +{ +} + +void ShaderGenVisitor::setRootStateSet(osg::StateSet *stateSet) +{ + if (_rootStateSet.valid()) + _state->removeStateSet(0); + _rootStateSet = stateSet; + if (_rootStateSet.valid()) + _state->pushStateSet(_rootStateSet.get()); +} + +void ShaderGenVisitor::reset() +{ + _state->popAllStateSets(); + if (_rootStateSet.valid()) + _state->pushStateSet(_rootStateSet.get()); +} + +void ShaderGenVisitor::apply(osg::Node &node) +{ + osg::StateSet *stateSet = node.getStateSet(); + + if (stateSet) + _state->pushStateSet(stateSet); + + traverse(node); + + if (stateSet) + _state->popStateSet(); +} + +void ShaderGenVisitor::apply(osg::Geode &geode) +{ + osg::StateSet *stateSet = geode.getStateSet(); + if (stateSet) + _state->pushStateSet(stateSet); + + for (unsigned int i=0; igetStateSet(); + if (ss) + _state->pushStateSet(ss); + + update(drawable); + + if (ss) + _state->popStateSet(); + } + + if (stateSet) + _state->popStateSet(); +} + +void ShaderGenVisitor::update(osg::Drawable *drawable) +{ + // update only geometry due to compatibility issues with user defined drawables + osg::Geometry *geometry = drawable->asGeometry(); + if (!geometry) + return; + + StateEx *state = static_cast(_state.get()); + // skip nodes without state sets + if (state->getStateSetStackSize() == (_rootStateSet.valid() ? 1 : 0)) + return; + + // skip state sets with already attached programs + if (state->getAttribute(osg::StateAttribute::PROGRAM)) + return; + + unsigned int stateMask = 0; + //if (state->getMode(GL_BLEND) & osg::StateAttribute::ON) + // stateMask |= ShaderGen::BLEND; + if (state->getMode(GL_LIGHTING) & osg::StateAttribute::ON) + stateMask |= ShaderGenCache::LIGHTING; + if (state->getMode(GL_FOG) & osg::StateAttribute::ON) + stateMask |= ShaderGenCache::FOG; + if (state->getTextureAttribute(0, osg::StateAttribute::TEXTURE)) + stateMask |= ShaderGenCache::DIFFUSE_MAP; + if (state->getTextureAttribute(1, osg::StateAttribute::TEXTURE) && + geometry->getVertexAttribArray(6)) //tangent + stateMask |= ShaderGenCache::NORMAL_MAP; + + // Get program and uniforms for accumulated state. + osg::StateSet *progss = _stateCache->getOrCreateStateSet(stateMask); + // Set program and uniforms to the last state set. + osg::StateSet *ss = const_cast(state->getStateSetStack().back()); + ss->setAttribute(progss->getAttribute(osg::StateAttribute::PROGRAM)); + ss->setUniformList(progss->getUniformList()); + +}