From Eric Wing, add alternate backdrop implementations.

From Robert Osfield, updated naming  and copy constructor methods.
This commit is contained in:
Robert Osfield 2006-07-18 12:24:04 +00:00
parent f2d50d943b
commit 419e185895
5 changed files with 517 additions and 81 deletions

View File

@ -167,6 +167,13 @@ osg:: Node* createTextLeft(const osg::BoundingBox& bb)
#if 1 #if 1
text->setBackdropType(osgText::Text::OUTLINE); 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->setBackdropOffset(0.05f);
text->setBackdropColor(osg::Vec4(0.0f, 0.0f, 0.5f, 1.0f)); text->setBackdropColor(osg::Vec4(0.0f, 0.0f, 0.5f, 1.0f));
#endif #endif

View File

@ -72,7 +72,7 @@ class OSG_EXPORT PolygonOffset : public StateAttribute
static void setUnitsMultiplier(float multiplier); static void setUnitsMultiplier(float multiplier);
static float getUnitsMultiplier(); static float getUnitsMultiplier();
static bool areUnitsAndMultipliersSet(); static bool areFactorAndUnitsMultipliersSet();
/** Checks with the OpenGL driver to try and pick multiplier approrpriate for the hardware. /** Checks with the OpenGL driver to try and pick multiplier approrpriate for the hardware.
note, requires a valid graphics context to be current. */ note, requires a valid graphics context to be current. */

View File

@ -226,6 +226,14 @@ public:
NONE NONE
}; };
enum BackdropImplementation
{
POLYGON_OFFSET = 0,
NO_DEPTH_BUFFER,
DEPTH_RANGE,
STENCIL_BUFFER
};
/** /**
* BackdropType gives you a background shadow text behind your regular * BackdropType gives you a background shadow text behind your regular
* text. This helps give text extra contrast which can be useful when * text. This helps give text extra contrast which can be useful when
@ -280,6 +288,85 @@ public:
const osg::Vec4& getBackdropColor() const { return _backdropColor; } 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 enum ColorGradientMode
{ {
@ -476,7 +563,16 @@ protected:
void computeColorGradientsOverall() const; void computeColorGradientsOverall() const;
void computeColorGradientsPerCharacter() 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; BackdropType _backdropType;
BackdropImplementation _backdropImplementation;
float _backdropHorizontalOffset; float _backdropHorizontalOffset;
float _backdropVerticalOffset; float _backdropVerticalOffset;
osg::Vec4 _backdropColor; osg::Vec4 _backdropColor;

View File

@ -42,7 +42,7 @@ float PolygonOffset::getUnitsMultiplier()
return s_UnitsMultipler; return s_UnitsMultipler;
} }
bool PolygonOffset::areUnitsAndMultipliersSet() bool PolygonOffset::areFactorAndUnitsMultipliersSet()
{ {
return s_MultiplerSet; return s_MultiplerSet;
} }

View File

@ -46,6 +46,7 @@ Text::Text():
_kerningType(KERNING_DEFAULT), _kerningType(KERNING_DEFAULT),
_lineCount(0), _lineCount(0),
_backdropType(NONE), _backdropType(NONE),
_backdropImplementation(POLYGON_OFFSET),
_backdropHorizontalOffset(0.07f), _backdropHorizontalOffset(0.07f),
_backdropVerticalOffset(0.07f), _backdropVerticalOffset(0.07f),
_backdropColor(0.0f, 0.0f, 0.0f, 1.0f), _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), _color(text._color),
_drawMode(text._drawMode), _drawMode(text._drawMode),
_kerningType(text._kerningType), _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(); computeGlyphRepresentation();
} }
@ -1446,87 +1457,34 @@ void Text::drawImplementation(osg::State& state) const
state.disableAllVertexArrays(); 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(_backdropType != NONE)
{ {
if (!osg::PolygonOffset::areUnitsAndMultipliersSet()) switch(_backdropImplementation)
{ {
osg::PolygonOffset::setFactorAndUnitsMultipliersUsingBestGuessForDriver(); 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);
} }
// 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 else
{ {
backdrop_index = _backdropType; renderOnlyForegroundText(state);
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());
}
}
if(_backdropType != NONE)
{
glPopAttrib();
} }
} }
@ -1626,6 +1584,15 @@ void Text::setBackdropType(BackdropType type)
computeGlyphRepresentation(); computeGlyphRepresentation();
} }
void Text::setBackdropImplementation(BackdropImplementation implementation)
{
if (_backdropImplementation==implementation) return;
_backdropImplementation = implementation;
computeGlyphRepresentation();
}
void Text::setBackdropOffset(float offset) void Text::setBackdropOffset(float offset)
{ {
_backdropHorizontalOffset = 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();
}