Am 17.10.2017 um 18:06 schrieb Robert Osfield: > With ShaderGen it's there as stop gap to generate some shaders for a > subset of the fixed function pipeline that is common in a subset of the > OSG loaders. It is just a means of getting "something" on the screen on > platforms like GL core profile and GLESL 2+, it's not an all purpose > fully functioning solution.
The appended patch adds build in support of diffuse and specular textures for obj by extending ShaderGen and the obj plugin. Adding shaders is disabled by default and needs to be enabled with a plugin option: osgviewer -O useBuildInShader plane-with-uv-map.obj I added the changed files and the related git patches which contains explanations about how this has been implemented. A test obj file has also be added. The used images are not added but downloadable from http://www.rastertek.com/dx10tut21.html because I did not find any related copyright info. >With the shader_pipeline work one would need to have a top level shader >that is able to handle the diffuse and specular texture maps in an >appropriate way. The current top level shader I've worked on so far is >OpenSceneGraph-Data/shaders/shaderpipeline.vert and >shaderpipeline.frag, this is just early days though, but might give you >an idea where this functionality is going and how one might add support >for different texturing and lighting approaches. Looking at the patch I'm currently not sure if all ShaderGen state mask combinations are working correctly and the question here is if it would make more sense to refactor ShaderGen to use define based shaders instead of adding all the remaining combinations to the recent implementation. All the more because there are already 'define' based shaders available for osg (see https://github.com/OpenMW/openmw/tree/master/files/shaders) Regards Ralf
plane-with-uv-map.obj
Description: application/tgif
# Blender MTL File: 'plane-with-uv-map.blend' # Material Count: 1 newmtl Material.001 Ns 1000 Ka 0.000000 0.000000 0.000000 Kd 1.000000 1.000000 1.000000 Ks 1.000000 1.000000 1.000000 Ke 0.000000 0.000000 0.000000 Ni 1.000000 d 1.000000 illum 2 map_Kd diffuse.jpg map_Ks specular.jpg
From 62125d95e47105680bafa1eecf62fcaa75647b0d Mon Sep 17 00:00:00 2001 From: Ralf Habacker <[email protected]> Date: Fri, 20 Oct 2017 10:33:46 +0200 Subject: [PATCH 2/3] Add support for displaying diffuse and specular textures to obj plugin If enabled with the new plugin option 'useBuildInShader' the obj plugin is able to display diffuse and specular textures provided by obj file attributes map_Kd and map_Ks. The plugin tracks texture type and texture unit assignments on reading the obj file and installs an event handler after reading to create initial shader and to track lightning state changes. On state change the shaders are recreated to reflect recent state. --- src/osgPlugins/obj/ReaderWriterOBJ.cpp | 100 ++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/src/osgPlugins/obj/ReaderWriterOBJ.cpp b/src/osgPlugins/obj/ReaderWriterOBJ.cpp index 5bf00c3c8c..a7ab3055b0 100644 --- a/src/osgPlugins/obj/ReaderWriterOBJ.cpp +++ b/src/osgPlugins/obj/ReaderWriterOBJ.cpp @@ -41,16 +41,93 @@ #include <osgDB/FileUtils> #include <osgDB/FileNameUtils> +#include <osgGA/GUIEventHandler> + #include <osgUtil/TriStripVisitor> +#include <osgUtil/ShaderGen> #include <osgUtil/SmoothingVisitor> #include <osgUtil/Tessellator> +#include <osgViewer/Viewer> + #include "obj.h" #include "OBJWriterNodeVisitor.h" #include <map> #include <set> +/** + * @brief convert ReaderWriterOBJ related material type to ShaderGenCache related type + * @param ReaderWriterOBJ related material type + * @return ShaderGenCache related type + */ +osgUtil::ShaderGenCache::StateMask toShaderGenStateMask(obj::Material::Map::TextureMapType type) +{ + switch(type) + { + case obj::Material::Map::DIFFUSE: return osgUtil::ShaderGenCache::DIFFUSE_MAP; + case obj::Material::Map::SPECULAR: return osgUtil::ShaderGenCache::SPECULAR_MAP; + default: return osgUtil::ShaderGenCache::UNDEFINED; + } +} + +class EventHandler : public osgGA::GUIEventHandler +{ +public: + EventHandler(osgUtil::ShaderGenCache::TextureUnitsMap &map) + : _done(false), + _lightState(false) + { + _visitor = new osgUtil::ShaderGenVisitor(new osgUtil::ShaderGenCache(map)); + } + + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object* obj, osg::NodeVisitor*) + { + if (ea.getHandled()) + return false; + + if (!_visitor.get()) + return false; + + switch(ea.getEventType()) + { + case(osgGA::GUIEventAdapter::FRAME): + { + osgViewer::View *view = dynamic_cast<osgViewer::View*>(&aa); + if (!view) + return false; + + int lightState = view->getCamera()->getOrCreateStateSet()->getMode(GL_LIGHTING); + if (lightState != _lightState) + { + _done = false; + _lightState = lightState; + } + if (!_done) + { + osg::Node *node = obj->asNode(); + if (node) + { + _visitor->reset(); + _visitor->setRootStateSet(view->getCamera()->getOrCreateStateSet()); + node->accept(*_visitor); + _done = true; + } + return true; + } + return true; + } + default: + return false; + } + return false; + } + + osg::ref_ptr<osgUtil::ShaderGenVisitor> _visitor; + bool _done; + int _lightState; +}; + class ReaderWriterOBJ : public osgDB::ReaderWriter { public: @@ -62,6 +139,7 @@ public: supportsOption("noTriStripPolygons","Do not do the default tri stripping of polygons"); supportsOption("generateFacetNormals","generate facet normals for verticies without normals"); supportsOption("noReverseFaces","avoid to reverse faces when normals and triangles orientation are reversed"); + supportsOption("useBuildInShader","use build in shader for displaying textures (currently diffuse and specular)"); supportsOption("DIFFUSE=<unit>", "Set texture unit for diffuse texture"); supportsOption("AMBIENT=<unit>", "Set texture unit for ambient texture"); @@ -141,10 +219,12 @@ protected: bool generateFacetNormals; bool fixBlackMaterials; bool noReverseFaces; + bool useBuildInShader; // This is the order in which the materials will be assigned to texture maps, unless // otherwise overriden typedef std::vector< std::pair<int,obj::Material::Map::TextureMapType> > TextureAllocationMap; TextureAllocationMap textureUnitAllocation; + osgUtil::ShaderGenCache::TextureUnitsMap shaderGenUnitsMap; ObjOptionsStruct() { @@ -154,6 +234,7 @@ protected: generateFacetNormals = false; fixBlackMaterials = true; noReverseFaces = false; + useBuildInShader = false; } }; @@ -380,7 +461,11 @@ void ReaderWriterOBJ::buildMaterialToStateSetMap(obj::Model& model, MaterialToSt break; } } - if(index>=0) load_material_texture( model, material.maps[index], stateset.get(), unit, options ); + if(index>=0) + { + load_material_texture( model, material.maps[index], stateset.get(), unit, options ); + localOptions.shaderGenUnitsMap[unit] = toShaderGenStateMask(type); + } } } // If the user has set no options, then we load them up in the order contained in the enum. This @@ -404,6 +489,7 @@ void ReaderWriterOBJ::buildMaterialToStateSetMap(obj::Model& model, MaterialToSt if(index>=0) { load_material_texture( model, material.maps[index], stateset.get(), unit, options ); + localOptions.shaderGenUnitsMap[unit] = toShaderGenStateMask(type); unit++; } } @@ -873,6 +959,10 @@ ReaderWriterOBJ::ObjOptionsStruct ReaderWriterOBJ::parseOptions(const osgDB::Rea { localOptions.noReverseFaces = true; } + else if (pre_equals == "useBuildInShader") + { + localOptions.useBuildInShader = true; + } else if (post_equals.length()>0) { obj::Material::Map::TextureMapType type = obj::Material::Map::UNKNOWN; @@ -922,8 +1012,10 @@ osgDB::ReaderWriter::ReadResult ReaderWriterOBJ::readNode(const std::string& fil model.readOBJ(fin, local_opt.get()); ObjOptionsStruct localOptions = parseOptions(options); - osg::Node* node = convertModelToSceneGraph(model, localOptions, local_opt.get()); + + if (localOptions.useBuildInShader) + node->addEventCallback(new EventHandler(localOptions.shaderGenUnitsMap)); return node; } @@ -940,8 +1032,10 @@ osgDB::ReaderWriter::ReadResult ReaderWriterOBJ::readNode(std::istream& fin, con model.readOBJ(fin, options); ObjOptionsStruct localOptions = parseOptions(options); + osg::Node* node= convertModelToSceneGraph(model, localOptions, options); - osg::Node* node = convertModelToSceneGraph(model, localOptions, options); + if (localOptions.useBuildInShader) + node->addEventCallback(new EventHandler(localOptions.shaderGenUnitsMap)); return node; } -- 2.12.3
From 6f20ad0a4b4b34b5d7a3bd298897dd69eb99e6f6 Mon Sep 17 00:00:00 2001 From: Ralf Habacker <[email protected]> Date: Fri, 22 Sep 2017 13:39:22 +0200 Subject: [PATCH 1/3] Add support for displaying specular textures to osgUtil::ShaderGenCache This patch adds a type osgUtil::ShaderGenCache::TextureUnitsMap to hold assignments between texture units and texture types. These assignment could be specified in the constructor and are handled inside the class to provide required information to shaders for displaying diffuse and specular textures using the Phong shading model for one point light (for details see https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/lighting.php). Skipping state sets with already attached programs has been removed because to reflect state changes it is required that ShaderGenCache recreates the shader on each run. --- include/osgUtil/ShaderGen | 14 ++++- src/osgUtil/ShaderGen.cpp | 142 +++++++++++++++++++++++++++++++--------------- 2 files changed, 108 insertions(+), 48 deletions(-) diff --git a/include/osgUtil/ShaderGen b/include/osgUtil/ShaderGen index 9beb717c35..6a544e5774 100644 --- a/include/osgUtil/ShaderGen +++ b/include/osgUtil/ShaderGen @@ -31,25 +31,33 @@ class OSGUTIL_EXPORT ShaderGenCache : public osg::Referenced public: enum StateMask { + UNDEFINED = 0, 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 + DIFFUSE_MAP = 8, //< Texture in unit 0 if not TextureUnitsMap has been provided + NORMAL_MAP = 16, //< Texture in unit 1 and vertex attribute array 6 if not TextureUnitsMap has been provided + SPECULAR_MAP = 32 }; typedef std::map<int, osg::ref_ptr<osg::StateSet> > StateSetMap; + typedef std::map<int, enum StateMask> TextureUnitsMap; - ShaderGenCache() {}; + ShaderGenCache(const TextureUnitsMap &textureUnits = TextureUnitsMap()) + : _textureUnits(textureUnits) + { + } void setStateSet(int stateMask, osg::StateSet *program); osg::StateSet *getStateSet(int stateMask) const; osg::StateSet *getOrCreateStateSet(int stateMask); + TextureUnitsMap &getTextureUnits(); protected: osg::StateSet *createStateSet(int stateMask) const; mutable OpenThreads::Mutex _mutex; StateSetMap _stateSetMap; + TextureUnitsMap _textureUnits; }; diff --git a/src/osgUtil/ShaderGen.cpp b/src/osgUtil/ShaderGen.cpp index 25329f83cc..f2e2f925d0 100644 --- a/src/osgUtil/ShaderGen.cpp +++ b/src/osgUtil/ShaderGen.cpp @@ -110,6 +110,11 @@ osg::StateSet *ShaderGenCache::getOrCreateStateSet(int stateMask) return it->second.get(); } +ShaderGenCache::TextureUnitsMap &ShaderGenCache::getTextureUnits() +{ + return _textureUnits; +} + osg::StateSet *ShaderGenCache::createStateSet(int stateMask) const { osg::StateSet *stateSet = new osg::StateSet; @@ -149,21 +154,31 @@ osg::StateSet *ShaderGenCache::createStateSet(int stateMask) const frag << vert_frag.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) + TextureUnitsMap::const_iterator it = _textureUnits.begin(); + for (; it != _textureUnits.end(); it++) { - 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"; + if (it->second == DIFFUSE_MAP && (stateMask & DIFFUSE_MAP)) + { + osg::Uniform *diffuseMap = new osg::Uniform("diffuseMap", it->first); + stateSet->addUniform(diffuseMap); + frag << "uniform sampler2D diffuseMap;\n"; + } + + if (it->second == SPECULAR_MAP && (stateMask & LIGHTING) && (stateMask & SPECULAR_MAP)) + { + osg::Uniform *specularMap = new osg::Uniform("specularMap", it->first); + stateSet->addUniform(specularMap); + frag << "uniform sampler2D specularMap;\n"; + } + + if (it->second == NORMAL_MAP && (stateMask & NORMAL_MAP)) + { + osg::Uniform *normalMap = new osg::Uniform("normalMap", it->first); + stateSet->addUniform(normalMap); + frag << "uniform sampler2D normalMap;\n"; + program->addBindAttribLocation("tangent", 6); + vert << "attribute vec3 tangent;\n"; + } } vert << "\n"\ @@ -171,8 +186,9 @@ osg::StateSet *ShaderGenCache::createStateSet(int stateMask) const "{\n"\ " gl_Position = ftransform();\n"; - if (stateMask & (DIFFUSE_MAP | NORMAL_MAP)) + if ((stateMask & DIFFUSE_MAP) || (stateMask & NORMAL_MAP) || (stateMask & SPECULAR_MAP)) { + // TODO support different texture sizes by using the related uniform index vert << " gl_TexCoord[0] = gl_MultiTexCoord0;\n"; } @@ -206,7 +222,8 @@ osg::StateSet *ShaderGenCache::createStateSet(int stateMask) const " if (lpos.w == 0.0)\n"\ " lightDir = lpos.xyz;\n"\ " else\n"\ - " lightDir = lpos.xyz + dir;\n"; + " lightDir = lpos.xyz + dir;\n"\ + " vertexColor = gl_Color;\n"; } else if (stateMask & FOG) { @@ -228,36 +245,51 @@ osg::StateSet *ShaderGenCache::createStateSet(int stateMask) const if (stateMask & DIFFUSE_MAP) { - frag << " vec4 base = vertexColor * texture2D(diffuseMap, gl_TexCoord[0].st);\n"; + frag << " vec4 base = texture2D(diffuseMap, gl_TexCoord[0].st);\n"; } else { frag << " vec4 base = vertexColor;\n"; } + if (stateMask & LIGHTING) + { + if (stateMask & SPECULAR_MAP) + { + frag << " vec4 spec = texture2D(specularMap, gl_TexCoord[0].st);\n"; + } + else + { + frag << " vec4 spec = vec4(1.0);\n"; + } + } + if (stateMask & NORMAL_MAP) { frag << " vec3 normalDir = texture2D(normalMap, gl_TexCoord[0].st).xyz*2.0-1.0;\n"; } #if !OSG_GLES2_FEATURES && !OSG_GLES3_FEATURES && !OSG_GL2_FEATURES - if (stateMask & (LIGHTING | NORMAL_MAP)) + if (stateMask & LIGHTING) { - 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"; + if ((stateMask & NORMAL_MAP) || (stateMask & DIFFUSE_MAP) || (stateMask & SPECULAR_MAP)) + { + frag << + " vec3 nd = normalize(normalDir);\n"\ + " vec3 ld = normalize(lightDir);\n"\ + " vec3 vd = normalize(viewDir);\n"\ + " vec4 Iamb = gl_FrontLightProduct[0].ambient;\n"\ + " float diff = max(dot(ld, nd), 0.0);\n"\ + " vec4 Idiff = gl_FrontLightProduct[0].diffuse * diff;\n"\ + " vec4 Ispec; \n"\ + " if (diff > 0.0)\n"\ + " {\n"\ + " vec3 halfDir = normalize(ld+vd);\n"\ + " Ispec = gl_FrontLightProduct[0].specular * \n"\ + " pow(max(dot(halfDir, nd), 0.0), gl_FrontMaterial.shininess);\n"\ + " }\n" + " vec4 color = gl_FrontLightModelProduct.sceneColor + Iamb + Idiff * base + Ispec * spec;\n"; + } } else #endif @@ -265,7 +297,6 @@ osg::StateSet *ShaderGenCache::createStateSet(int stateMask) const frag << " vec4 color = base;\n"; } - #if !OSG_GLES2_FEATURES && !OSG_GLES3_FEATURES && !OSG_GL2_FEATURES if (stateMask & FOG) { @@ -371,10 +402,6 @@ void ShaderGenVisitor::update(osg::Drawable *drawable) if (state->getStateSetStackSize() == (_rootStateSet.valid() ? 1u : 0u)) return; - // skip state sets with already attached programs - if (state->getAttribute(osg::StateAttribute::PROGRAM)) - return; - int stateMask = 0; //if (state->getMode(GL_BLEND) & osg::StateAttribute::ON) // stateMask |= ShaderGen::BLEND; @@ -382,12 +409,25 @@ void ShaderGenVisitor::update(osg::Drawable *drawable) 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!=0 && - geometry->getVertexAttribArray(6)) //tangent - stateMask |= ShaderGenCache::NORMAL_MAP; + ShaderGenCache::TextureUnitsMap &_textureUnits = _stateCache->getTextureUnits(); + if (_textureUnits.empty()) + { + if (state->getTextureAttribute(0, osg::StateAttribute::TEXTURE)) + stateMask |= ShaderGenCache::DIFFUSE_MAP; + if (state->getTextureAttribute(1, osg::StateAttribute::TEXTURE) && geometry!=0 && + geometry->getVertexAttribArray(6)) //tangent + stateMask |= ShaderGenCache::NORMAL_MAP; + } + else + { + ShaderGenCache::TextureUnitsMap::const_iterator it = _textureUnits.begin(); + for (;it != _textureUnits.end(); it++) + { + if (state->getTextureAttribute(it->first, osg::StateAttribute::TEXTURE)) + stateMask |= it->second; + } + } // Get program and uniforms for accumulated state. osg::StateSet *progss = _stateCache->getOrCreateStateSet(stateMask); @@ -406,6 +446,18 @@ void ShaderGenVisitor::update(osg::Drawable *drawable) { ss->removeMode(GL_FOG); } - if ((stateMask&ShaderGenCache::DIFFUSE_MAP)!=0) ss->removeTextureMode(0, GL_TEXTURE_2D); - if ((stateMask&ShaderGenCache::NORMAL_MAP)!=0) ss->removeTextureMode(1, GL_TEXTURE_2D); + if (_textureUnits.empty()) + { + if ((stateMask&ShaderGenCache::DIFFUSE_MAP)!=0) ss->removeTextureMode(0, GL_TEXTURE_2D); + if ((stateMask&ShaderGenCache::NORMAL_MAP)!=0) ss->removeTextureMode(1, GL_TEXTURE_2D); + } + else + { + ShaderGenCache::TextureUnitsMap::const_iterator it = _textureUnits.begin(); + for (;it != _textureUnits.end(); it++) + { + if ((stateMask & it->second)!=0) + ss->removeTextureMode(it->first, GL_TEXTURE_2D); + } + } } -- 2.12.3
/* -*-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 <osgUtil/Export>
#include <osg/NodeVisitor>
#include <osg/State>
namespace osgUtil
{
class OSGUTIL_EXPORT ShaderGenCache : public osg::Referenced
{
public:
enum StateMask
{
UNDEFINED = 0,
BLEND = 1,
LIGHTING = 2,
FOG = 4,
DIFFUSE_MAP = 8, //< Texture in unit 0 if not TextureUnitsMap has been
provided
NORMAL_MAP = 16, //< Texture in unit 1 and vertex attribute array 6 if
not TextureUnitsMap has been provided
SPECULAR_MAP = 32
};
typedef std::map<int, osg::ref_ptr<osg::StateSet> > StateSetMap;
typedef std::map<int, enum StateMask> TextureUnitsMap;
ShaderGenCache(const TextureUnitsMap &textureUnits = TextureUnitsMap())
: _textureUnits(textureUnits)
{
}
void setStateSet(int stateMask, osg::StateSet *program);
osg::StateSet *getStateSet(int stateMask) const;
osg::StateSet *getOrCreateStateSet(int stateMask);
TextureUnitsMap &getTextureUnits();
protected:
osg::StateSet *createStateSet(int stateMask) const;
mutable OpenThreads::Mutex _mutex;
StateSetMap _stateSetMap;
TextureUnitsMap _textureUnits;
};
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<ShaderGenCache> _stateCache;
osg::ref_ptr<osg::State> _state;
osg::ref_ptr<osg::StateSet> _rootStateSet;
};
}
#endif
// -*-c++-*- /* * Wavefront OBJ loader for Open Scene Graph * * Copyright (C) 2001,2007 Ulrich Hertlein <[email protected]> * * Modified by Robert Osfield to support per Drawable coord, normal and * texture coord arrays, bug fixes, and support for texture mapping. * * Writing support added 2007 by Stephan Huber, http://digitalmind.de, * some ideas taken from the dae-plugin * * The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for * real-time rendering of large 3D photo-realistic models. * The OSG homepage is http://www.openscenegraph.org/ */ #if defined(_MSC_VER) #pragma warning( disable : 4786 ) #endif #include <stdlib.h> #include <string> #include <osg/Notify> #include <osg/Node> #include <osg/MatrixTransform> #include <osg/Geode> #include <osg/Vec3f> #include <osg/Geometry> #include <osg/StateSet> #include <osg/Material> #include <osg/Texture2D> #include <osg/TexGen> #include <osg/TexMat> #include <osgDB/Registry> #include <osgDB/ReadFile> #include <osgDB/FileUtils> #include <osgDB/FileNameUtils> #include <osgGA/GUIEventHandler> #include <osgUtil/TriStripVisitor> #include <osgUtil/ShaderGen> #include <osgUtil/SmoothingVisitor> #include <osgUtil/Tessellator> #include <osgViewer/Viewer> #include "obj.h" #include "OBJWriterNodeVisitor.h" #include <map> #include <set> /** * @brief convert ReaderWriterOBJ related material type to ShaderGenCache related type * @param ReaderWriterOBJ related material type * @return ShaderGenCache related type */ osgUtil::ShaderGenCache::StateMask toShaderGenStateMask(obj::Material::Map::TextureMapType type) { switch(type) { case obj::Material::Map::DIFFUSE: return osgUtil::ShaderGenCache::DIFFUSE_MAP; case obj::Material::Map::SPECULAR: return osgUtil::ShaderGenCache::SPECULAR_MAP; default: return osgUtil::ShaderGenCache::UNDEFINED; } } class EventHandler : public osgGA::GUIEventHandler { public: EventHandler(osgUtil::ShaderGenCache::TextureUnitsMap &map) : _done(false), _lightState(false) { _visitor = new osgUtil::ShaderGenVisitor(new osgUtil::ShaderGenCache(map)); } bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object* obj, osg::NodeVisitor*) { if (ea.getHandled()) return false; if (!_visitor.get()) return false; switch(ea.getEventType()) { case(osgGA::GUIEventAdapter::FRAME): { osgViewer::View *view = dynamic_cast<osgViewer::View*>(&aa); if (!view) return false; int lightState = view->getCamera()->getOrCreateStateSet()->getMode(GL_LIGHTING); if (lightState != _lightState) { _done = false; _lightState = lightState; } if (!_done) { osg::Node *node = obj->asNode(); if (node) { _visitor->reset(); _visitor->setRootStateSet(view->getCamera()->getOrCreateStateSet()); node->accept(*_visitor); _done = true; } return true; } return true; } default: return false; } return false; } osg::ref_ptr<osgUtil::ShaderGenVisitor> _visitor; bool _done; int _lightState; }; class ReaderWriterOBJ : public osgDB::ReaderWriter { public: ReaderWriterOBJ() { supportsExtension("obj","Alias Wavefront OBJ format"); supportsOption("noRotation","Do not do the default rotate about X axis"); supportsOption("noTesselateLargePolygons","Do not do the default tesselation of large polygons"); supportsOption("noTriStripPolygons","Do not do the default tri stripping of polygons"); supportsOption("generateFacetNormals","generate facet normals for verticies without normals"); supportsOption("noReverseFaces","avoid to reverse faces when normals and triangles orientation are reversed"); supportsOption("useBuildInShader","use build in shader for displaying textures (currently diffuse and specular)"); supportsOption("DIFFUSE=<unit>", "Set texture unit for diffuse texture"); supportsOption("AMBIENT=<unit>", "Set texture unit for ambient texture"); supportsOption("SPECULAR=<unit>", "Set texture unit for specular texture"); supportsOption("SPECULAR_EXPONENT=<unit>", "Set texture unit for specular exponent texture"); supportsOption("OPACITY=<unit>", "Set texture unit for opacity/dissolve texture"); supportsOption("BUMP=<unit>", "Set texture unit for bumpmap texture"); supportsOption("DISPLACEMENT=<unit>", "Set texture unit for displacement texture"); supportsOption("REFLECTION=<unit>", "Set texture unit for reflection texture"); } virtual const char* className() const { return "Wavefront OBJ Reader"; } virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const; virtual ReadResult readNode(std::istream& fin, const Options* options) const; virtual WriteResult writeObject(const osg::Object& obj,const std::string& fileName,const Options* options=NULL) const { const osg::Node* node = dynamic_cast<const osg::Node*>(&obj); if (node) return writeNode(*node, fileName, options); else return WriteResult(WriteResult::FILE_NOT_HANDLED); } virtual WriteResult writeNode(const osg::Node& node,const std::string& fileName,const Options* options =NULL) const { if (!acceptsExtension(osgDB::getFileExtension(fileName))) return WriteResult(WriteResult::FILE_NOT_HANDLED); osgDB::ofstream f(fileName.c_str()); std::string materialFile = osgDB::getNameLessExtension(fileName) + ".mtl"; OBJWriterNodeVisitor nv(f, osgDB::getSimpleFileName(materialFile)); // we must cast away constness (const_cast<osg::Node*>(&node))->accept(nv); osgDB::ofstream mf(materialFile.c_str()); nv.writeMaterials(mf); return WriteResult(WriteResult::FILE_SAVED); } virtual WriteResult writeObject(const osg::Object& obj,std::ostream& fout,const Options* options=NULL) const { const osg::Node* node = dynamic_cast<const osg::Node*>(&obj); if (node) return writeNode(*node, fout, options); else return WriteResult(WriteResult::FILE_NOT_HANDLED); } virtual WriteResult writeNode(const osg::Node& node,std::ostream& fout,const Options* =NULL) const { // writing to a stream does not support materials OBJWriterNodeVisitor nv(fout); // we must cast away constness (const_cast<osg::Node*>(&node))->accept(nv); return WriteResult(WriteResult::FILE_SAVED); } protected: class ObjOptionsStruct { public: bool rotate; bool noTesselateLargePolygons; bool noTriStripPolygons; bool generateFacetNormals; bool fixBlackMaterials; bool noReverseFaces; bool useBuildInShader; // This is the order in which the materials will be assigned to texture maps, unless // otherwise overriden typedef std::vector< std::pair<int,obj::Material::Map::TextureMapType> > TextureAllocationMap; TextureAllocationMap textureUnitAllocation; osgUtil::ShaderGenCache::TextureUnitsMap shaderGenUnitsMap; ObjOptionsStruct() { rotate = true; noTesselateLargePolygons = false; noTriStripPolygons = false; generateFacetNormals = false; fixBlackMaterials = true; noReverseFaces = false; useBuildInShader = false; } }; typedef std::map< std::string, osg::ref_ptr<osg::StateSet> > MaterialToStateSetMap; void buildMaterialToStateSetMap(obj::Model& model, MaterialToStateSetMap& materialToSetSetMapObj, ObjOptionsStruct& localOptions, const Options* options) const; osg::Geometry* convertElementListToGeometry(obj::Model& model, obj::Model::ElementList& elementList, ObjOptionsStruct& localOptions) const; osg::Node* convertModelToSceneGraph(obj::Model& model, ObjOptionsStruct& localOptions, const Options* options) const; inline osg::Vec3 transformVertex(const osg::Vec3& vec, const bool rotate) const ; inline osg::Vec3 transformNormal(const osg::Vec3& vec, const bool rotate) const ; ObjOptionsStruct parseOptions(const Options* options) const; }; inline osg::Vec3 ReaderWriterOBJ::transformVertex(const osg::Vec3& vec, const bool rotate) const { if(rotate==true) { return osg::Vec3(vec.x(),-vec.z(),vec.y()); } else { return vec; } } inline osg::Vec3 ReaderWriterOBJ::transformNormal(const osg::Vec3& vec, const bool rotate) const { if(rotate==true) { return osg::Vec3(vec.x(),-vec.z(),vec.y()); } else { return vec; } } // register with Registry to instantiate the above reader/writer. REGISTER_OSGPLUGIN(obj, ReaderWriterOBJ) static void load_material_texture( obj::Model &model, obj::Material::Map &map, osg::StateSet *stateset, const unsigned int texture_unit, const osgDB::Options* options) { std::string filename = map.name; if (!filename.empty()) { osg::ref_ptr< osg::Image > image; if ( !model.getDatabasePath().empty() ) { // first try with database path of parent. image = osgDB::readRefImageFile(model.getDatabasePath()+'/'+filename, options); } if ( !image.valid() ) { // if not already set then try the filename as is. image = osgDB::readRefImageFile(filename, options); } if ( image.valid() ) { osg::Texture2D* texture = new osg::Texture2D( image.get() ); osg::Texture::WrapMode textureWrapMode; if(map.clamp == true) { textureWrapMode = osg::Texture::CLAMP_TO_BORDER; texture->setBorderColor(osg::Vec4(0.0,0.0,0.0,0.0)); // transparent //stateset->setMode(GL_BLEND, osg::StateAttribute::ON); //stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); } else { textureWrapMode = osg::Texture::REPEAT; } texture->setWrap(osg::Texture2D::WRAP_R, textureWrapMode); texture->setWrap(osg::Texture2D::WRAP_S, textureWrapMode); texture->setWrap(osg::Texture2D::WRAP_T, textureWrapMode); stateset->setTextureAttributeAndModes( texture_unit, texture,osg::StateAttribute::ON ); if ( map.type == obj::Material::Map::REFLECTION ) { osg::TexGen* texgen = new osg::TexGen; texgen->setMode(osg::TexGen::SPHERE_MAP); stateset->setTextureAttributeAndModes( texture_unit,texgen,osg::StateAttribute::ON ); } if ( image->isImageTranslucent()) { OSG_INFO<<"Found transparent image"<<std::endl; stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); } } } if (map.uScale != 1.0f || map.vScale != 1.0f || map.uOffset != 0.0f || map.vOffset != 0.0f) { osg::Matrix mat; if (map.uScale != 1.0f || map.vScale != 1.0f) { OSG_DEBUG << "Obj TexMat scale=" << map.uScale << "," << map.vScale << std::endl; mat *= osg::Matrix::scale(map.uScale, map.vScale, 1.0); } if (map.uOffset != 0.0f || map.vOffset != 0.0f) { OSG_DEBUG << "Obj TexMat offset=" << map.uOffset << "," << map.uOffset << std::endl; mat *= osg::Matrix::translate(map.uOffset, map.vOffset, 0.0); } osg::TexMat* texmat = new osg::TexMat; texmat->setMatrix(mat); stateset->setTextureAttributeAndModes( texture_unit,texmat,osg::StateAttribute::ON ); } } void ReaderWriterOBJ::buildMaterialToStateSetMap(obj::Model& model, MaterialToStateSetMap& materialToStateSetMap, ObjOptionsStruct& localOptions, const Options* options) const { if (localOptions.fixBlackMaterials) { // hack to fix Maya exported models that contian all black materials. int numBlack = 0; int numNotBlack = 0; obj::Model::MaterialMap::iterator itr; for(itr = model.materialMap.begin(); itr != model.materialMap.end(); ++itr) { obj::Material& material = itr->second; if (material.ambient==osg::Vec4(0.0f,0.0f,0.0f,1.0f) && material.diffuse==osg::Vec4(0.0f,0.0f,0.0f,1.0f)) { ++numBlack; } else { ++numNotBlack; } } if (numNotBlack==0 && numBlack!=0) { for(itr = model.materialMap.begin(); itr != model.materialMap.end(); ++itr) { obj::Material& material = itr->second; if (material.ambient==osg::Vec4(0.0f,0.0f,0.0f,1.0f) && material.diffuse==osg::Vec4(0.0f,0.0f,0.0f,1.0f)) { material.ambient.set(0.3f,0.3f,0.3f,1.0f); material.diffuse.set(1.0f,1.0f,1.0f,1.0f); } } } } for(obj::Model::MaterialMap::iterator itr = model.materialMap.begin(); itr != model.materialMap.end(); ++itr) { obj::Material& material = itr->second; osg::ref_ptr< osg::StateSet > stateset = new osg::StateSet; bool isTransparent = false; // handle material colors // http://java3d.j3d.org/utilities/loaders/obj/sun.html if (material.illum != 0) { osg::Material* osg_material = new osg::Material; stateset->setAttribute(osg_material); osg_material->setName(material.name); osg_material->setAmbient(osg::Material::FRONT_AND_BACK,material.ambient); osg_material->setDiffuse(osg::Material::FRONT_AND_BACK,material.diffuse); osg_material->setEmission(osg::Material::FRONT_AND_BACK,material.emissive); if (material.illum == 2) { osg_material->setSpecular(osg::Material::FRONT_AND_BACK,material.specular); } else { osg_material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4(0,0,0,1)); } osg_material->setShininess(osg::Material::FRONT_AND_BACK,(material.Ns/1000.0f)*128.0f ); // note OBJ shiniess is 0..1000. if (material.ambient[3]!=1.0 || material.diffuse[3]!=1.0 || material.specular[3]!=1.0|| material.emissive[3]!=1.0) { OSG_INFO<<"Found transparent material"<<std::endl; isTransparent = true; } } // If the user has explicitly set the required texture type to unit map via the options // string, then we load ONLY those textures that are in the map. if(localOptions.textureUnitAllocation.size()>0) { for(unsigned int i=0;i<localOptions.textureUnitAllocation.size();i++) { // firstly, get the option set pair int unit = localOptions.textureUnitAllocation[i].first; obj::Material::Map::TextureMapType type = localOptions.textureUnitAllocation[i].second; // secondly, see if this texture type (e.g. DIFFUSE) is one of those in the material int index = -1; for(unsigned int j=0;j<material.maps.size();j++) { if(material.maps[j].type == type) { index = (int) j; break; } } if(index>=0) { load_material_texture( model, material.maps[index], stateset.get(), unit, options ); localOptions.shaderGenUnitsMap[unit] = toShaderGenStateMask(type); } } } // If the user has set no options, then we load them up in the order contained in the enum. This // latter method is an attempt not to break user's existing code else { int unit = 0; for(int i=0;i<(int) obj::Material::Map::UNKNOWN;i++) // for each type { obj::Material::Map::TextureMapType type = (obj::Material::Map::TextureMapType) i; // see if this texture type (e.g. DIFFUSE) is one of those in the material int index = -1; for(unsigned int j=0;j<material.maps.size();j++) { if(material.maps[j].type == type) { index = (int) j; break; } } if(index>=0) { load_material_texture( model, material.maps[index], stateset.get(), unit, options ); localOptions.shaderGenUnitsMap[unit] = toShaderGenStateMask(type); unit++; } } } if (isTransparent) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); } materialToStateSetMap[material.name] = stateset.get(); } } osg::Geometry* ReaderWriterOBJ::convertElementListToGeometry(obj::Model& model, obj::Model::ElementList& elementList, ObjOptionsStruct& localOptions) const { unsigned int numVertexIndices = 0; unsigned int numNormalIndices = 0; unsigned int numTexCoordIndices = 0; unsigned int numPointElements = 0; unsigned int numPolylineElements = 0; unsigned int numPolygonElements = 0; obj::Model::ElementList::iterator itr; if (localOptions.generateFacetNormals == true) { for(itr=elementList.begin(); itr!=elementList.end(); ++itr) { obj::Element& element = *(*itr); if (element.dataType==obj::Element::POINTS || element.dataType==obj::Element::POLYLINE) continue; if (element.normalIndices.size() == 0) { // fill in the normals int a = element.vertexIndices[0]; int b = element.vertexIndices[1]; int c = element.vertexIndices[2]; osg::Vec3f ab(model.vertices[b]); osg::Vec3f ac(model.vertices[c]); ab -= model.vertices[a]; ac -= model.vertices[a]; osg::Vec3f Norm( ab ^ ac ); Norm.normalize(); int normal_idx = model.normals.size(); model.normals.push_back(Norm); for (unsigned i=0 ; i < element.vertexIndices.size() ; i++) element.normalIndices.push_back(normal_idx); } } } for(itr=elementList.begin(); itr!=elementList.end(); ++itr) { obj::Element& element = *(*itr); numVertexIndices += element.vertexIndices.size(); numNormalIndices += element.normalIndices.size(); numTexCoordIndices += element.texCoordIndices.size(); numPointElements += (element.dataType==obj::Element::POINTS) ? 1 : 0; numPolylineElements += (element.dataType==obj::Element::POLYLINE) ? 1 : 0; numPolygonElements += (element.dataType==obj::Element::POLYGON) ? 1 : 0; } if (numVertexIndices==0) return 0; if (numNormalIndices!=0 && numNormalIndices!=numVertexIndices) { OSG_NOTICE<<"Incorrect number of normals, ignore them"<<std::endl; numNormalIndices = 0; } if (numTexCoordIndices!=0 && numTexCoordIndices!=numVertexIndices) { OSG_NOTICE<<"Incorrect number of normals, ignore them"<<std::endl; numTexCoordIndices = 0; } osg::Vec3Array* vertices = numVertexIndices ? new osg::Vec3Array : 0; osg::Vec3Array* normals = numNormalIndices ? new osg::Vec3Array : 0; osg::Vec2Array* texcoords = numTexCoordIndices ? new osg::Vec2Array : 0; osg::Vec4Array* colors = (!model.colors.empty()) ? new osg::Vec4Array : 0; if (vertices) vertices->reserve(numVertexIndices); if (normals) normals->reserve(numNormalIndices); if (texcoords) texcoords->reserve(numTexCoordIndices); if (colors) colors->reserve(numVertexIndices); osg::Geometry* geometry = new osg::Geometry; if (vertices) geometry->setVertexArray(vertices); if (normals) { geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); } if (texcoords) { geometry->setTexCoordArray(0,texcoords); } if (colors) { geometry->setColorArray(colors); geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); } if (numPointElements>0) { unsigned int startPos = vertices->size(); unsigned int numPoints = 0; for(itr=elementList.begin(); itr!=elementList.end(); ++itr) { obj::Element& element = *(*itr); if (element.dataType==obj::Element::POINTS) { for(obj::Element::IndexList::iterator index_itr = element.vertexIndices.begin(); index_itr != element.vertexIndices.end(); ++index_itr) { // if use color extension ( not standard but used by meshlab) if (colors) { colors->push_back(model.colors[*index_itr]); } vertices->push_back(transformVertex(model.vertices[*index_itr],localOptions.rotate)); ++numPoints; } if (numNormalIndices) { for(obj::Element::IndexList::iterator index_itr = element.normalIndices.begin(); index_itr != element.normalIndices.end(); ++index_itr) { normals->push_back(transformNormal(model.normals[*index_itr],localOptions.rotate)); } } if (numTexCoordIndices) { for(obj::Element::IndexList::iterator index_itr = element.texCoordIndices.begin(); index_itr != element.texCoordIndices.end(); ++index_itr) { texcoords->push_back(model.texcoords[*index_itr]); } } } } osg::DrawArrays* drawArrays = new osg::DrawArrays(GL_POINTS,startPos,numPoints); geometry->addPrimitiveSet(drawArrays); } if (numPolylineElements>0) { unsigned int startPos = vertices->size(); osg::DrawArrayLengths* drawArrayLengths = new osg::DrawArrayLengths(GL_LINES,startPos); for(itr=elementList.begin(); itr!=elementList.end(); ++itr) { obj::Element& element = *(*itr); if (element.dataType==obj::Element::POLYLINE) { drawArrayLengths->push_back(element.vertexIndices.size()); for(obj::Element::IndexList::iterator index_itr = element.vertexIndices.begin(); index_itr != element.vertexIndices.end(); ++index_itr) { // if use color extension ( not standard but used by meshlab) if (colors) { colors->push_back(model.colors[*index_itr]); } vertices->push_back(transformVertex(model.vertices[*index_itr],localOptions.rotate)); } if (numNormalIndices) { for(obj::Element::IndexList::iterator index_itr = element.normalIndices.begin(); index_itr != element.normalIndices.end(); ++index_itr) { normals->push_back(transformNormal(model.normals[*index_itr],localOptions.rotate)); } } if (numTexCoordIndices) { for(obj::Element::IndexList::iterator index_itr = element.texCoordIndices.begin(); index_itr != element.texCoordIndices.end(); ++index_itr) { texcoords->push_back(model.texcoords[*index_itr]); } } } } geometry->addPrimitiveSet(drawArrayLengths); } // #define USE_DRAWARRAYLENGTHS bool hasReversedFaces = false ; if (numPolygonElements>0) { unsigned int startPos = vertices->size(); #ifdef USE_DRAWARRAYLENGTHS osg::DrawArrayLengths* drawArrayLengths = new osg::DrawArrayLengths(GL_POLYGON,startPos); geometry->addPrimitiveSet(drawArrayLengths); #endif for(itr=elementList.begin(); itr!=elementList.end(); ++itr) { obj::Element& element = *(*itr); if (element.dataType==obj::Element::POLYGON) { #ifdef USE_DRAWARRAYLENGTHS drawArrayLengths->push_back(element.vertexIndices.size()); #else if (element.vertexIndices.size()>4) { osg::DrawArrays* drawArrays = new osg::DrawArrays(GL_POLYGON,startPos,element.vertexIndices.size()); startPos += element.vertexIndices.size(); geometry->addPrimitiveSet(drawArrays); } else { osg::DrawArrays* drawArrays = new osg::DrawArrays(GL_TRIANGLE_FAN,startPos,element.vertexIndices.size()); startPos += element.vertexIndices.size(); geometry->addPrimitiveSet(drawArrays); } #endif if (model.needReverse(element) && !localOptions.noReverseFaces) { hasReversedFaces = true; // need to reverse so add to OSG arrays in same order as in OBJ, as OSG assume anticlockwise ordering. for(obj::Element::IndexList::reverse_iterator index_itr = element.vertexIndices.rbegin(); index_itr != element.vertexIndices.rend(); ++index_itr) { // if use color extension ( not standard but used by meshlab) if (colors) { colors->push_back(model.colors[*index_itr]); } vertices->push_back(transformVertex(model.vertices[*index_itr],localOptions.rotate)); } if (numNormalIndices) { for(obj::Element::IndexList::reverse_iterator index_itr = element.normalIndices.rbegin(); index_itr != element.normalIndices.rend(); ++index_itr) { normals->push_back(transformNormal(model.normals[*index_itr],localOptions.rotate)); } } if (numTexCoordIndices) { for(obj::Element::IndexList::reverse_iterator index_itr = element.texCoordIndices.rbegin(); index_itr != element.texCoordIndices.rend(); ++index_itr) { texcoords->push_back(model.texcoords[*index_itr]); } } } else { // no need to reverse so add to OSG arrays in same order as in OBJ. for(obj::Element::IndexList::iterator index_itr = element.vertexIndices.begin(); index_itr != element.vertexIndices.end(); ++index_itr) { // if use color extension ( not standard but used by meshlab) if (colors) { colors->push_back(model.colors[*index_itr]); } vertices->push_back(transformVertex(model.vertices[*index_itr],localOptions.rotate)); } if (numNormalIndices) { for(obj::Element::IndexList::iterator index_itr = element.normalIndices.begin(); index_itr != element.normalIndices.end(); ++index_itr) { normals->push_back(transformNormal(model.normals[*index_itr],localOptions.rotate)); } } if (numTexCoordIndices) { for(obj::Element::IndexList::iterator index_itr = element.texCoordIndices.begin(); index_itr != element.texCoordIndices.end(); ++index_itr) { texcoords->push_back(model.texcoords[*index_itr]); } } } } } } if(hasReversedFaces) { OSG_WARN << "Warning: [ReaderWriterOBJ::convertElementListToGeometry] Some faces from geometry '" << geometry->getName() << "' were reversed by the plugin" << std::endl; } return geometry; } osg::Node* ReaderWriterOBJ::convertModelToSceneGraph(obj::Model& model, ObjOptionsStruct& localOptions, const Options* options) const { if (model.elementStateMap.empty()) return 0; osg::Group* group = new osg::Group; // set up the materials MaterialToStateSetMap materialToStateSetMap; buildMaterialToStateSetMap(model, materialToStateSetMap, localOptions, options); // go through the groups of related elements and build geometry from them. for(obj::Model::ElementStateMap::iterator itr=model.elementStateMap.begin(); itr!=model.elementStateMap.end(); ++itr) { const obj::ElementState& es = itr->first; obj::Model::ElementList& el = itr->second; osg::Geometry* geometry = convertElementListToGeometry(model,el,localOptions); if (geometry) { MaterialToStateSetMap::const_iterator it = materialToStateSetMap.find(es.materialName); if (it == materialToStateSetMap.end()) { OSG_WARN << "Obj unable to find material '" << es.materialName << "'" << std::endl; } osg::StateSet* stateset = materialToStateSetMap[es.materialName].get(); geometry->setStateSet(stateset); // tesseleate any large polygons if (!localOptions.noTesselateLargePolygons) { osgUtil::Tessellator tessellator; tessellator.retessellatePolygons(*geometry); } // tri strip polygons to improve graphics peformance if (!localOptions.noTriStripPolygons) { osgUtil::TriStripVisitor tsv; tsv.stripify(*geometry); } // if no normals present add them. if (localOptions.generateFacetNormals==false && (!geometry->getNormalArray() || geometry->getNormalArray()->getNumElements()==0)) { osgUtil::SmoothingVisitor sv; sv.smooth(*geometry); } osg::Geode* geode = new osg::Geode; geode->addDrawable(geometry); if (es.objectName.empty()) { geode->setName(es.groupName); } else if (es.groupName.empty()) { geode->setName(es.objectName); } else { geode->setName(es.groupName + std::string(":") + es.objectName); } group->addChild(geode); } } return group; } ReaderWriterOBJ::ObjOptionsStruct ReaderWriterOBJ::parseOptions(const osgDB::ReaderWriter::Options* options) const { ObjOptionsStruct localOptions; if (options!=NULL) { std::istringstream iss(options->getOptionString()); std::string opt; while (iss >> opt) { // split opt into pre= and post= std::string pre_equals; std::string post_equals; size_t found = opt.find("="); if(found!=std::string::npos) { pre_equals = opt.substr(0,found); post_equals = opt.substr(found+1); } else { pre_equals = opt; } if (pre_equals == "noRotation") { localOptions.rotate = false; } else if (pre_equals == "noTesselateLargePolygons") { localOptions.noTesselateLargePolygons = true; } else if (pre_equals == "noTriStripPolygons") { localOptions.noTriStripPolygons = true; } else if (pre_equals == "generateFacetNormals") { localOptions.generateFacetNormals = true; } else if (pre_equals == "noReverseFaces") { localOptions.noReverseFaces = true; } else if (pre_equals == "useBuildInShader") { localOptions.useBuildInShader = true; } else if (post_equals.length()>0) { obj::Material::Map::TextureMapType type = obj::Material::Map::UNKNOWN; // Now we check to see if we have anything forcing a texture allocation if (pre_equals == "DIFFUSE") type = obj::Material::Map::DIFFUSE; else if (pre_equals == "AMBIENT") type = obj::Material::Map::AMBIENT; else if (pre_equals == "SPECULAR") type = obj::Material::Map::SPECULAR; else if (pre_equals == "SPECULAR_EXPONENT") type = obj::Material::Map::SPECULAR_EXPONENT; else if (pre_equals == "OPACITY") type = obj::Material::Map::OPACITY; else if (pre_equals == "BUMP") type = obj::Material::Map::BUMP; else if (pre_equals == "DISPLACEMENT") type = obj::Material::Map::DISPLACEMENT; else if (pre_equals == "REFLECTION") type = obj::Material::Map::REFLECTION; if (type!=obj::Material::Map::UNKNOWN) { int unit = atoi(post_equals.c_str()); // (probably should use istringstream rather than atoi) localOptions.textureUnitAllocation.push_back(std::make_pair(unit,(obj::Material::Map::TextureMapType) type)); OSG_NOTICE<<"Obj Found map in options, ["<<pre_equals<<"]="<<unit<<std::endl; } } } } return localOptions; } // read file and convert to OSG. osgDB::ReaderWriter::ReadResult ReaderWriterOBJ::readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const { std::string ext = osgDB::getLowerCaseFileExtension(file); if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED; std::string fileName = osgDB::findDataFile( file, options ); if (fileName.empty()) return ReadResult::FILE_NOT_FOUND; osgDB::ifstream fin(fileName.c_str()); if (fin) { // code for setting up the database path so that internally referenced file are searched for on relative paths. osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options; local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileName)); obj::Model model; model.setDatabasePath(osgDB::getFilePath(fileName.c_str())); model.readOBJ(fin, local_opt.get()); ObjOptionsStruct localOptions = parseOptions(options); osg::Node* node = convertModelToSceneGraph(model, localOptions, local_opt.get()); if (localOptions.useBuildInShader) node->addEventCallback(new EventHandler(localOptions.shaderGenUnitsMap)); return node; } return ReadResult::FILE_NOT_HANDLED; } osgDB::ReaderWriter::ReadResult ReaderWriterOBJ::readNode(std::istream& fin, const Options* options) const { if (fin) { fin.imbue(std::locale::classic()); obj::Model model; model.readOBJ(fin, options); ObjOptionsStruct localOptions = parseOptions(options); osg::Node* node= convertModelToSceneGraph(model, localOptions, options); if (localOptions.useBuildInShader) node->addEventCallback(new EventHandler(localOptions.shaderGenUnitsMap)); return node; } return ReadResult::FILE_NOT_HANDLED; }
/* -*-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 <osgUtil/ShaderGen>
#include <osg/Geode>
#include <osg/Geometry> // for ShaderGenVisitor::update
#include <osg/Fog>
#include <sstream>
using namespace osgUtil;
#undef OSG_GL2_FEATURES
#define OSG_GL2_FEATURES 0
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<osg::Uniform *>(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<osg::StateAttribute*>(it->second.attributeVec.back().first) : 0;
}
};
}
void ShaderGenCache::setStateSet(int stateMask, osg::StateSet *stateSet)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
_stateSetMap[stateMask] = stateSet;
}
osg::StateSet *ShaderGenCache::getStateSet(int stateMask) const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
StateSetMap::const_iterator it = _stateSetMap.find(stateMask);
return (it != _stateSetMap.end()) ? it->second.get() : 0;
}
osg::StateSet *ShaderGenCache::getOrCreateStateSet(int stateMask)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
StateSetMap::iterator it = _stateSetMap.find(stateMask);
if (it == _stateSetMap.end())
{
osg::StateSet *stateSet = createStateSet(stateMask);
_stateSetMap.insert(it, StateSetMap::value_type(stateMask, stateSet));
return stateSet;
}
return it->second.get();
}
ShaderGenCache::TextureUnitsMap &ShaderGenCache::getTextureUnits()
{
return _textureUnits;
}
osg::StateSet *ShaderGenCache::createStateSet(int stateMask) const
{
osg::StateSet *stateSet = new osg::StateSet;
osg::Program *program = new osg::Program;
stateSet->setAttribute(program);
std::ostringstream vert_frag;
vert_frag << "// ShaderGen shader\n";
vert_frag << "#ifdef GL_ES\n"
" precision highp float;\n"
"#endif\n";
// write varyings
if ((stateMask & LIGHTING) && !(stateMask & NORMAL_MAP))
{
vert_frag << "varying vec3 normalDir;\n";
}
if (stateMask & (LIGHTING | NORMAL_MAP))
{
vert_frag << "varying vec3 lightDir;\n";
}
if (stateMask & (LIGHTING | NORMAL_MAP | FOG))
{
vert_frag << "varying vec3 viewDir;\n";
}
vert_frag << "varying vec4 vertexColor;\n";
std::ostringstream vert;
std::ostringstream frag;
// copy varying to vertex ad fragment shader
vert << vert_frag.str();
frag << vert_frag.str();
// write uniforms and attributes
TextureUnitsMap::const_iterator it = _textureUnits.begin();
for (; it != _textureUnits.end(); it++)
{
if (it->second == DIFFUSE_MAP && (stateMask & DIFFUSE_MAP))
{
osg::Uniform *diffuseMap = new osg::Uniform("diffuseMap", it->first);
stateSet->addUniform(diffuseMap);
frag << "uniform sampler2D diffuseMap;\n";
}
if (it->second == SPECULAR_MAP && (stateMask & LIGHTING) && (stateMask & SPECULAR_MAP))
{
osg::Uniform *specularMap = new osg::Uniform("specularMap", it->first);
stateSet->addUniform(specularMap);
frag << "uniform sampler2D specularMap;\n";
}
if (it->second == NORMAL_MAP && (stateMask & NORMAL_MAP))
{
osg::Uniform *normalMap = new osg::Uniform("normalMap", it->first);
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) || (stateMask & NORMAL_MAP) || (stateMask & SPECULAR_MAP))
{
// TODO support different texture sizes by using the related uniform index
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";
}
#if !OSG_GLES2_FEATURES && !OSG_GLES3_FEATURES && !OSG_GL2_FEATURES
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"\
" vertexColor = gl_Color;\n";
}
else if (stateMask & FOG)
{
vert <<
" viewDir = -vec3(gl_ModelViewMatrix * gl_Vertex);\n"\
" vertexColor = gl_Color;\n";
}
#endif
else
{
vert << " vertexColor = 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 = vertexColor;\n";
}
if (stateMask & LIGHTING)
{
if (stateMask & SPECULAR_MAP)
{
frag << " vec4 spec = texture2D(specularMap, gl_TexCoord[0].st);\n";
}
else
{
frag << " vec4 spec = vec4(1.0);\n";
}
}
if (stateMask & NORMAL_MAP)
{
frag << " vec3 normalDir = texture2D(normalMap, gl_TexCoord[0].st).xyz*2.0-1.0;\n";
}
#if !OSG_GLES2_FEATURES && !OSG_GLES3_FEATURES && !OSG_GL2_FEATURES
if (stateMask & LIGHTING)
{
if ((stateMask & NORMAL_MAP) || (stateMask & DIFFUSE_MAP) || (stateMask & SPECULAR_MAP))
{
frag <<
" vec3 nd = normalize(normalDir);\n"\
" vec3 ld = normalize(lightDir);\n"\
" vec3 vd = normalize(viewDir);\n"\
" vec4 Iamb = gl_FrontLightProduct[0].ambient;\n"\
" float diff = max(dot(ld, nd), 0.0);\n"\
" vec4 Idiff = gl_FrontLightProduct[0].diffuse * diff;\n"\
" vec4 Ispec; \n"\
" if (diff > 0.0)\n"\
" {\n"\
" vec3 halfDir = normalize(ld+vd);\n"\
" Ispec = gl_FrontLightProduct[0].specular * \n"\
" pow(max(dot(halfDir, nd), 0.0), gl_FrontMaterial.shininess);\n"\
" }\n"
" vec4 color = gl_FrontLightModelProduct.sceneColor + Iamb + Idiff * base + Ispec * spec;\n";
}
}
else
#endif
{
frag << " vec4 color = base;\n";
}
#if !OSG_GLES2_FEATURES && !OSG_GLES3_FEATURES && !OSG_GL2_FEATURES
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";
}
#endif
frag << " gl_FragColor = color;\n";
frag << "}\n";
std::string vertstr = vert.str();
std::string fragstr = frag.str();
OSG_DEBUG << "ShaderGenCache Vertex shader:\n" << vertstr << std::endl;
OSG_DEBUG << "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; i<geode.getNumDrawables(); ++i)
{
osg::Drawable *drawable = geode.getDrawable(i);
osg::StateSet *ss = drawable->getStateSet();
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 0
if (!geometry)
return;
#endif
StateEx *state = static_cast<StateEx *>(_state.get());
// skip nodes without state sets
if (state->getStateSetStackSize() == (_rootStateSet.valid() ? 1u : 0u))
return;
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;
ShaderGenCache::TextureUnitsMap &_textureUnits = _stateCache->getTextureUnits();
if (_textureUnits.empty())
{
if (state->getTextureAttribute(0, osg::StateAttribute::TEXTURE))
stateMask |= ShaderGenCache::DIFFUSE_MAP;
if (state->getTextureAttribute(1, osg::StateAttribute::TEXTURE) && geometry!=0 &&
geometry->getVertexAttribArray(6)) //tangent
stateMask |= ShaderGenCache::NORMAL_MAP;
}
else
{
ShaderGenCache::TextureUnitsMap::const_iterator it = _textureUnits.begin();
for (;it != _textureUnits.end(); it++)
{
if (state->getTextureAttribute(it->first, osg::StateAttribute::TEXTURE))
stateMask |= it->second;
}
}
// 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<osg::StateSet *>(state->getStateSetStack().back());
ss->setAttribute(progss->getAttribute(osg::StateAttribute::PROGRAM));
ss->setUniformList(progss->getUniformList());
// remove any modes that won't be appropriate when using shaders
if ((stateMask&ShaderGenCache::LIGHTING)!=0)
{
ss->removeMode(GL_LIGHTING);
ss->removeMode(GL_LIGHT0);
}
if ((stateMask&ShaderGenCache::FOG)!=0)
{
ss->removeMode(GL_FOG);
}
if (_textureUnits.empty())
{
if ((stateMask&ShaderGenCache::DIFFUSE_MAP)!=0) ss->removeTextureMode(0, GL_TEXTURE_2D);
if ((stateMask&ShaderGenCache::NORMAL_MAP)!=0) ss->removeTextureMode(1, GL_TEXTURE_2D);
}
else
{
ShaderGenCache::TextureUnitsMap::const_iterator it = _textureUnits.begin();
for (;it != _textureUnits.end(); it++)
{
if ((stateMask & it->second)!=0)
ss->removeTextureMode(it->first, GL_TEXTURE_2D);
}
}
}
_______________________________________________ osg-submissions mailing list [email protected] http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
