diff --git a/examples/osglogo/osglogo.cpp b/examples/osglogo/osglogo.cpp index 5daefd369..036e618ff 100644 --- a/examples/osglogo/osglogo.cpp +++ b/examples/osglogo/osglogo.cpp @@ -167,6 +167,13 @@ osg:: Node* createTextLeft(const osg::BoundingBox& bb) #if 1 text->setBackdropType(osgText::Text::OUTLINE); +// text->setBackdropType(osgText::Text::DROP_SHADOW_BOTTOM_RIGHT); + + text->setBackdropImplementation(osgText::Text::POLYGON_OFFSET); +// text->setBackdropImplementation(osgText::Text::NO_DEPTH_BUFFER); +// text->setBackdropImplementation(osgText::Text::DEPTH_RANGE); +// text->setBackdropImplementation(osgText::Text::STENCIL_BUFFER); + text->setBackdropOffset(0.05f); text->setBackdropColor(osg::Vec4(0.0f, 0.0f, 0.5f, 1.0f)); #endif diff --git a/include/osg/PolygonOffset b/include/osg/PolygonOffset index 7e2d9852a..df2f5f774 100644 --- a/include/osg/PolygonOffset +++ b/include/osg/PolygonOffset @@ -72,7 +72,7 @@ class OSG_EXPORT PolygonOffset : public StateAttribute static void setUnitsMultiplier(float multiplier); static float getUnitsMultiplier(); - static bool areUnitsAndMultipliersSet(); + static bool areFactorAndUnitsMultipliersSet(); /** Checks with the OpenGL driver to try and pick multiplier approrpriate for the hardware. note, requires a valid graphics context to be current. */ diff --git a/include/osgText/Text b/include/osgText/Text index 10d177aa1..cda696474 100644 --- a/include/osgText/Text +++ b/include/osgText/Text @@ -226,6 +226,14 @@ public: NONE }; + enum BackdropImplementation + { + POLYGON_OFFSET = 0, + NO_DEPTH_BUFFER, + DEPTH_RANGE, + STENCIL_BUFFER + }; + /** * BackdropType gives you a background shadow text behind your regular * text. This helps give text extra contrast which can be useful when @@ -280,6 +288,85 @@ public: const osg::Vec4& getBackdropColor() const { return _backdropColor; } + /** + * This specifies the underlying backdrop rendering implementation. + * Unfortunately, at this time, there is no "perfect" rendering solution + * so this function is provided to let you 'pick your poison'. Each + * implementation has trade-offs. + * + * POLYGON_OFFSET: + * This uses glPolygonOffset to draw the text multiple times to + * create the drop-shadow and outline effects. glPolygonOffset + * is used to prevent z-fighting of the overlapping text. + * This probably should have been the best option, but all the ATI + * cards we have encountered so far have serious problems with this. + * We see little white holes/artifacts in the rendered glyph textures + * which move around depending on the viewing angle. For moving text, + * the moving holes give an extremely unpleasant flickering effect. + * Pumping up the "units" parameter in glPolygonOffset can minimize + * this problem, but two other bad side-effects occur if you do this. + * First, high values will cause problems with clipping, particularly + * when there are objects behind the text. The drop-shadows or outline + * may be culled because their computed offset is behind the object or + * z-far plane. Second, there is an additional problem associated with + * the Z-slope. High values can make large chunks of the backdrop + * suddenly disappear. This can be reduced by the "factor" parameter. + * Making the "factor" value small, can help, but experimentally, we've + * found that it creates a new, different kind of z-fighting problem. + * So there is no perfect solution. With units, you trade off the 'holes' + * for the large-section clipping. + * Experimentally, we have found units values from 150-512 to be tolerable + * to acceptable with respect to the 'holes'. A factor of .1 seems to + * bring down the large clipping problem without creating a new z-fighting + * problem. + * (You can experiment with these numbers by playing with the + * osg:PolygonOffset multipliers which this backend tries to respect.) + * + * If ATI ever fixes their cards/drivers, then this might become the + * best option. + * + * + * NO_DEPTH_BUFFER + * Instead of using glPolygonOffset to prevent z-fighting, this mode + * just disables the depth buffer when rendering the text. This allows + * the text to be rendered without any z-fighting. The downside to this + * mode is that render order begins to matter and the text will not + * necessarily correctly appear above or behind other objects in the + * scene based on depth values. + * This mode is best for text that only needs to be ontop and + * not obscured by any objects. + * + * DEPTH_RANGE + * This mode is inspired by Paul Martz's OpenGL FAQ, item 13.050. + * This uses glDepthRange as a substitute for glPolygonOffset. + * Strangely, experiments on ATI cards seem to produce cleaner results + * than when using glPolygonOffset. The trade-off for this is that the + * backdrop still may be placed too far back and might be culled by objects + * directly behind the object or by the far z-plane. If ATI ever fixes + * the glPolygonOffset problem, polygon offset is probably a slightly + * better solution because you can use smaller offsets. But with the + * current ATI problem, this option may be preferable. + * + * STENCIL_BUFFER + * (Assuming the backend is written correctly,) the Stencil Buffer is + * the most "correct" and reliable way of producing backdrop text. + * The stencil buffer is a multipass system that allows writing to the + * same z-values without needing to resort to offsets. This implementation + * should not have any of the problems associated with the 3 previous + * implementations. But the trade-off for this mode is that without + * hardware acceleration for the stencil buffer, rendering will be + * extremely slow. (There is also potentially more overhead for this + * algorithm so it could be slower than the other implementations. + * Benchmarking would be required to determine if the speed differences + * are significant on your particular hardware.) This mode is best for + * when quality is important and stencil buffer hardware acceleration + * is available. + */ + void setBackdropImplementation(BackdropImplementation implementation); + + BackdropImplementation getBackdropImplementation() const { return _backdropImplementation; } + + enum ColorGradientMode { @@ -470,13 +557,22 @@ protected: void computePositions(); void computePositions(unsigned int contextID) const; - + void computeBackdropPositions(unsigned int contextID) const; void computeColorGradients() const; void computeColorGradientsOverall() const; void computeColorGradientsPerCharacter() const; + void drawForegroundText(osg::State& state, const GlyphQuads& glyphquad) const; + void renderOnlyForegroundText(osg::State& state) const; + void renderWithPolygonOffset(osg::State& state) const; + void renderWithNoDepthBuffer(osg::State& state) const; + void renderWithDepthRange(osg::State& state) const; + void renderWithStencilBuffer(osg::State& state) const; + BackdropType _backdropType; + BackdropImplementation _backdropImplementation; + float _backdropHorizontalOffset; float _backdropVerticalOffset; osg::Vec4 _backdropColor; diff --git a/src/osg/PolygonOffset.cpp b/src/osg/PolygonOffset.cpp index 7906650d8..f4272d625 100644 --- a/src/osg/PolygonOffset.cpp +++ b/src/osg/PolygonOffset.cpp @@ -42,7 +42,7 @@ float PolygonOffset::getUnitsMultiplier() return s_UnitsMultipler; } -bool PolygonOffset::areUnitsAndMultipliersSet() +bool PolygonOffset::areFactorAndUnitsMultipliersSet() { return s_MultiplerSet; } diff --git a/src/osgText/Text.cpp b/src/osgText/Text.cpp index 7c946a4cb..b55f41639 100644 --- a/src/osgText/Text.cpp +++ b/src/osgText/Text.cpp @@ -46,6 +46,7 @@ Text::Text(): _kerningType(KERNING_DEFAULT), _lineCount(0), _backdropType(NONE), + _backdropImplementation(POLYGON_OFFSET), _backdropHorizontalOffset(0.07f), _backdropVerticalOffset(0.07f), _backdropColor(0.0f, 0.0f, 0.0f, 1.0f), @@ -79,7 +80,17 @@ Text::Text(const Text& text,const osg::CopyOp& copyop): _color(text._color), _drawMode(text._drawMode), _kerningType(text._kerningType), - _lineCount(text._lineCount) + _lineCount(text._lineCount), + _backdropType(text._backdropType), + _backdropImplementation(text._backdropImplementation), + _backdropHorizontalOffset(text._backdropHorizontalOffset), + _backdropVerticalOffset(text._backdropVerticalOffset), + _backdropColor(text._backdropColor), + _colorGradientMode(text._colorGradientMode), + _colorGradientTopLeft(text._colorGradientTopLeft), + _colorGradientBottomLeft(text._colorGradientBottomLeft), + _colorGradientBottomRight(text._colorGradientBottomRight), + _colorGradientTopRight(text._colorGradientTopRight) { computeGlyphRepresentation(); } @@ -1446,88 +1457,35 @@ void Text::drawImplementation(osg::State& state) const state.disableAllVertexArrays(); + // Okay, since ATI's cards/drivers are not working correctly, + // we need alternative solutions to glPolygonOffset. + // So this is a pick your poison approach. Each alternative + // backend has trade-offs associated with it, but with luck, + // the user may find that works for them. if(_backdropType != NONE) { - if (!osg::PolygonOffset::areUnitsAndMultipliersSet()) + switch(_backdropImplementation) { - osg::PolygonOffset::setFactorAndUnitsMultipliersUsingBestGuessForDriver(); - } - - // Do I really need to do this for glPolygonOffset? - glPushAttrib(GL_POLYGON_OFFSET_FILL); - glEnable(GL_POLYGON_OFFSET_FILL); - } - - for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin(); - titr!=_textureGlyphQuadMap.end(); - ++titr) - { - // need to set the texture here... - state.apply(titr->first.get()); - - const GlyphQuads& glyphquad = titr->second; - - // For backdrop text - if(_backdropType != NONE) - { - unsigned int backdrop_index; - unsigned int max_backdrop_index; - if(_backdropType == OUTLINE) - { - backdrop_index = 0; - max_backdrop_index = 8; - } - else - { - backdrop_index = _backdropType; - max_backdrop_index = _backdropType+1; - } - - state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); - state.disableColorPointer(); - glColor4fv(_backdropColor.ptr()); - - for( ; backdrop_index < max_backdrop_index; backdrop_index++) - { - const GlyphQuads::Coords3& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index][contextID]; - if (!transformedBackdropCoords.empty()) - { - state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedBackdropCoords.front())); - glPolygonOffset(2.0f * osg::PolygonOffset::getFactorMultiplier(), - 3.0f * osg::PolygonOffset::getUnitsMultiplier() * (max_backdrop_index-backdrop_index) ); - glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); - } - } - - glPolygonOffset(0.0f,0.0f); - - } // end of backdrop text - - const GlyphQuads::Coords3& transformedCoords = glyphquad._transformedCoords[contextID]; - if (!transformedCoords.empty()) - { - state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedCoords.front())); - state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); - - if(_colorGradientMode == SOLID) - { - state.disableColorPointer(); - glColor4fv(_color.ptr()); - } - else - { - state.setColorPointer( 4, GL_FLOAT, 0, &(glyphquad._colorCoords.front())); - } - - glDrawArrays(GL_QUADS,0,transformedCoords.size()); - + case POLYGON_OFFSET: + renderWithPolygonOffset(state); + break; + case NO_DEPTH_BUFFER: + renderWithNoDepthBuffer(state); + break; + case DEPTH_RANGE: + renderWithDepthRange(state); + break; + case STENCIL_BUFFER: + renderWithStencilBuffer(state); + break; + default: + renderWithPolygonOffset(state); } } - - if(_backdropType != NONE) + else { - glPopAttrib(); - } + renderOnlyForegroundText(state); + } } if (_drawMode & BOUNDINGBOX) @@ -1626,6 +1584,15 @@ void Text::setBackdropType(BackdropType type) computeGlyphRepresentation(); } +void Text::setBackdropImplementation(BackdropImplementation implementation) +{ + if (_backdropImplementation==implementation) return; + + _backdropImplementation = implementation; + computeGlyphRepresentation(); +} + + void Text::setBackdropOffset(float offset) { _backdropHorizontalOffset = offset; @@ -1859,7 +1826,373 @@ void Text::convertRgbToHsv( float rgb[], float hsv[] ) const } - +void Text::drawForegroundText(osg::State& state, const GlyphQuads& glyphquad) const +{ + unsigned int contextID = state.getContextID(); + + const GlyphQuads::Coords3& transformedCoords = glyphquad._transformedCoords[contextID]; + if (!transformedCoords.empty()) + { + state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedCoords.front())); + state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); + + if(_colorGradientMode == SOLID) + { + state.disableColorPointer(); + glColor4fv(_color.ptr()); + } + else + { + state.setColorPointer( 4, GL_FLOAT, 0, &(glyphquad._colorCoords.front())); + } + + glDrawArrays(GL_QUADS,0,transformedCoords.size()); + + } +} + +void Text::renderOnlyForegroundText(osg::State& state) const +{ + for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin(); + titr!=_textureGlyphQuadMap.end(); + ++titr) + { + // need to set the texture here... + state.apply(titr->first.get()); + + const GlyphQuads& glyphquad = titr->second; + + drawForegroundText(state, glyphquad); + } + +} + + +void Text::renderWithPolygonOffset(osg::State& state) const +{ +// glNormal3fv(_normal.ptr()); + +// state.disableAllVertexArrays(); + unsigned int contextID = state.getContextID(); + + + if (!osg::PolygonOffset::areFactorAndUnitsMultipliersSet()) + { + osg::PolygonOffset::setFactorAndUnitsMultipliersUsingBestGuessForDriver(); + } + + // Do I really need to do this for glPolygonOffset? + glPushAttrib(GL_POLYGON_OFFSET_FILL); + glEnable(GL_POLYGON_OFFSET_FILL); + + for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin(); + titr!=_textureGlyphQuadMap.end(); + ++titr) + { + // need to set the texture here... + state.apply(titr->first.get()); + + const GlyphQuads& glyphquad = titr->second; + + unsigned int backdrop_index; + unsigned int max_backdrop_index; + if(_backdropType == OUTLINE) + { + backdrop_index = 0; + max_backdrop_index = 8; + } + else + { + backdrop_index = _backdropType; + max_backdrop_index = _backdropType+1; + } + + state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); + state.disableColorPointer(); + glColor4fv(_backdropColor.ptr()); + + for( ; backdrop_index < max_backdrop_index; backdrop_index++) + { + const GlyphQuads::Coords3& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index][contextID]; + if (!transformedBackdropCoords.empty()) + { + state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedBackdropCoords.front())); + glPolygonOffset(0.1f * osg::PolygonOffset::getFactorMultiplier(), + 2.0f * osg::PolygonOffset::getUnitsMultiplier() * (max_backdrop_index-backdrop_index) ); + glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); + } + } + + // Reset the polygon offset so the foreground text is on top + glPolygonOffset(0.0f,0.0f); + + drawForegroundText(state, glyphquad); + } + + glPopAttrib(); +} + + +void Text::renderWithNoDepthBuffer(osg::State& state) const +{ + unsigned int contextID = state.getContextID(); + + glPushAttrib(GL_DEPTH_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + + for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin(); + titr!=_textureGlyphQuadMap.end(); + ++titr) + { + // need to set the texture here... + state.apply(titr->first.get()); + + const GlyphQuads& glyphquad = titr->second; + + unsigned int backdrop_index; + unsigned int max_backdrop_index; + if(_backdropType == OUTLINE) + { + backdrop_index = 0; + max_backdrop_index = 8; + } + else + { + backdrop_index = _backdropType; + max_backdrop_index = _backdropType+1; + } + + state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); + state.disableColorPointer(); + glColor4fv(_backdropColor.ptr()); + + for( ; backdrop_index < max_backdrop_index; backdrop_index++) + { + const GlyphQuads::Coords3& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index][contextID]; + if (!transformedBackdropCoords.empty()) + { + state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedBackdropCoords.front())); + glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); + } + } + + drawForegroundText(state, glyphquad); + } + + glPopAttrib(); +} + +// This idea comes from Paul Martz's OpenGL FAQ: 13.050 +void Text::renderWithDepthRange(osg::State& state) const +{ + unsigned int contextID = state.getContextID(); + + // Hmmm, the man page says GL_VIEWPORT_BIT for Depth range (near and far) + // but experimentally, GL_DEPTH_BUFFER_BIT for glDepthRange. +// glPushAttrib(GL_VIEWPORT_BIT); + glPushAttrib(GL_DEPTH_BUFFER_BIT); + + for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin(); + titr!=_textureGlyphQuadMap.end(); + ++titr) + { + // need to set the texture here... + state.apply(titr->first.get()); + + const GlyphQuads& glyphquad = titr->second; + + unsigned int backdrop_index; + unsigned int max_backdrop_index; + if(_backdropType == OUTLINE) + { + backdrop_index = 0; + max_backdrop_index = 8; + } + else + { + backdrop_index = _backdropType; + max_backdrop_index = _backdropType+1; + } + + state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); + state.disableColorPointer(); + glColor4fv(_backdropColor.ptr()); + + for( ; backdrop_index < max_backdrop_index; backdrop_index++) + { + const GlyphQuads::Coords3& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index][contextID]; + if (!transformedBackdropCoords.empty()) + { + state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedBackdropCoords.front())); + glDepthRange(0.01f + ((max_backdrop_index-backdrop_index)/1000.0f), 1.0); + + glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); + } + } + + glDepthRange(0.0, 0.982); + + drawForegroundText(state, glyphquad); + } + + glPopAttrib(); +} + +void Text::renderWithStencilBuffer(osg::State& state) const +{ + /* Here are the steps: + * 1) Disable drawing color + * 2) Enable the stencil buffer + * 3) Draw all the text to the stencil buffer + * 4) Disable the stencil buffer + * 5) Enable color + * 6) Disable the depth buffer + * 7) Draw all the text again. + * 7b) Make sure the foreground text is drawn last if priority levels + * are the same OR + * 7c) If priority levels are different, then make sure the foreground + * text has the higher priority. + */ + unsigned int contextID = state.getContextID(); + + glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_TEST); + + // It seems I can get away without calling this here + //glClear(GL_STENCIL_BUFFER_BIT); + + // enable stencil buffer + glEnable(GL_STENCIL_TEST); + + // write a one to the stencil buffer everywhere we are about to draw + glStencilFunc(GL_ALWAYS, 1, 1); + + // write only to the stencil buffer if we pass the depth test + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + + // Disable writing to the color buffer so we only write to the stencil + // buffer and the depth buffer + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + // make sure the depth buffer is enabled +// glEnable(GL_DEPTH_TEST); +// glDepthMask(GL_TRUE); +// glDepthFunc(GL_LESS); + + // Arrrgh! Why does the code only seem to work correctly if I call this? + glDepthMask(GL_FALSE); + + + // Draw all the text to the stencil buffer to mark out the region + // that we can write too. + + for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin(); + titr!=_textureGlyphQuadMap.end(); + ++titr) + { + // need to set the texture here... + state.apply(titr->first.get()); + + const GlyphQuads& glyphquad = titr->second; + + unsigned int backdrop_index; + unsigned int max_backdrop_index; + if(_backdropType == OUTLINE) + { + backdrop_index = 0; + max_backdrop_index = 8; + } + else + { + backdrop_index = _backdropType; + max_backdrop_index = _backdropType+1; + } + + state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); + state.disableColorPointer(); + + for( ; backdrop_index < max_backdrop_index; backdrop_index++) + { + const GlyphQuads::Coords3& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index][contextID]; + if (!transformedBackdropCoords.empty()) + { + state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedBackdropCoords.front())); + glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); + } + } + + // Draw the foreground text + const GlyphQuads::Coords3& transformedCoords = glyphquad._transformedCoords[contextID]; + if (!transformedCoords.empty()) + { + state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedCoords.front())); + state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); + glDrawArrays(GL_QUADS,0,transformedCoords.size()); + } + } + + + // disable the depth buffer +// glDisable(GL_DEPTH_TEST); +// glDepthMask(GL_FALSE); +// glDepthMask(GL_TRUE); +// glDepthFunc(GL_ALWAYS); + + // Set the stencil function to pass when the stencil is 1 + // Bug: This call seems to have no effect. Try changing to NOTEQUAL + // and see the exact same results. + glStencilFunc(GL_EQUAL, 1, 1); + + // disable writing to the stencil buffer + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilMask(GL_FALSE); + + // Re-enable writing to the color buffer so we can see the results + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + + // Draw all the text again + + for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin(); + titr!=_textureGlyphQuadMap.end(); + ++titr) + { + // need to set the texture here... + state.apply(titr->first.get()); + + const GlyphQuads& glyphquad = titr->second; + + unsigned int backdrop_index; + unsigned int max_backdrop_index; + if(_backdropType == OUTLINE) + { + backdrop_index = 0; + max_backdrop_index = 8; + } + else + { + backdrop_index = _backdropType; + max_backdrop_index = _backdropType+1; + } + + state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); + state.disableColorPointer(); + glColor4fv(_backdropColor.ptr()); + + for( ; backdrop_index < max_backdrop_index; backdrop_index++) + { + const GlyphQuads::Coords3& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index][contextID]; + if (!transformedBackdropCoords.empty()) + { + state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedBackdropCoords.front())); + glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); + } + } + + drawForegroundText(state, glyphquad); + } + + glPopAttrib(); +}