SDF Text Clipping


#1

Hi,
I am using the cool text library by @paul.houx for rendering text (https://github.com/paulhoux/Cinder-Samples/tree/master/TextRendering) on a 3D rectangular plane (camera can zoom pan tilt etc), and the signed distance field method works very well independent of camera zoom.

The problem i’m having is that I am not sure how to go about “clipping” the text, so that if the string would render larger than a specific size specified in world coords, then the part outside the limits won’t display, leaving the part inside on display.

I’ve tried using:

        gl::pushMatrices();

		Rectf textBounds = mTextBox.getBounds();			
        gl::translate(vec2(mCurrentPosition.value().x, mCurrentPosition.value().y));
        gl::rotate(glm::radians(mCurrentRotation), 0, 0, 1);
        gl::scale(vec2(2, 2));	

        mTextBox.setSize(getWidth(), getHeight());			
        mTextBox.setText("Text String");

		gl::ScopedScissor sC(textBounds.getUpperLeft(), textBounds.getSize());
        mTextBox.draw();

        gl::popMatrices();

The above code does not work correctly, but I’m not sure if its a Cinder question or a question related to Paul’s library. I haven’t really delved into this area before and was wondering if someone else has came across the same problem?

Cheers


#2

Hi,

Are you drawing in two 2d?
if so, cinder flips the y coord for the view matrix to make the (0,0) on the top-left corner of the window but gl::scissors uses the origin as the lower-left corner, so you will have to do some math to get that right:

something like this:

auto pos = vec2( 100, 100);
auto size = (mRect.getSize());
auto invPos = vec2( pos.x, getWindowHeight()) - pos.y - size.y ); // flip the y axis
gl::ScopedScissor sC( invPos, size );


#3

Hi,

I’m drawing in 3D so I would like the text to be clipped to world space instead of window/screen space coords. Eg. Have some text starting at 10000,10000 and then clipped to say width=300, height=50, for example. I guess this is the part I’m not sure how to code up.

Cheers


#4

I believe that that glScissor only works in screen coordinates, you can only do a rectangle parallel to the window…

https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glScissor.xml
you could transform the 3d coord to screen coordinates, but the glScissor wont follow the perspective

another option is to render the text in a FBO and use that


#5

Interesting idea about the FBO, however I have many places in the 3D plane where text is displayed and I fear it wont be performant. At the moment, I can use one instance of Paul’s TextBox class and then simply change the text properties of it and redraw at new location for each time I want text. This is cool, because I don’t have to store so many Text objects. There is just one (for potentially hundreds of on-screen text objects). I’m not sure if I could achieve this using FBO’s, would I not need 1 FBO per text object?

Looking at how Paul’s class renders it seems to be a VBO buffer, so I guess my problem is how to clip vertices in the VBO.
Cheers


#6

Hi,

nice to hear you’re having fun with SDF text. Note that I have since abandoned that specific kind of SDF text rendering in favour of multi-channel SDF’s. @chaoticbob created a cool Cinder Block for it, to which I contributed a much improved shader. If you find that you need even crispier text, have a look.

With that being said, let’s turn to the issue at hand. You may want to look into using the stencil buffer. This allows you to mask portions of your canvas. The upside is that this also works for non-rectangular areas and you don’t need to render to an Fbo, so your text stays crisp no matter what. The downside is that it is a boolean test, so it will be impossible to use an anti-aliased mask. This can be remedied using a post-process like FXAA or SMAA, but I’ll not go into that in this post.

By default, the stencil buffer is disabled for performance and memory reasons. Enable it using:

CINDER_APP( YourApp, RendererGl( RendererGl::Options().stencil() ) )

Then, before drawing your text, render a mask to the stencil buffer. This is as simple as setting up OpenGL to render to the stencil buffer, then rendering the shape you want:

// Disable color and depth buffers.
gl::colorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
gl::depthMask( GL_FALSE );

// Prepare stencil buffer.
auto ctx = gl::context();
ctx->pushBoolState( GL_STENCIL_TEST, GL_TRUE );
ctx->pushStencilFunc( GL_ALWAYS, 1, 0xFF );
ctx->pushStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
ctx->pushStencilMask( 0xFF );

gl::clear( GL_STENCIL_BUFFER_BIT );

// Now render your shape using polygons. No need to
// use a fragment shader, unless you want to do something
// fancy like `discard`-ing some pixels.

Next, setup OpenGL to render normally while enabling the stencil mask:

// Enable color and depth buffers.
gl::depthMask( GL_TRUE );
gl::colorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );

// Enable stencil testing.
gl::stencilFunc( GL_EQUAL, 1, 0xFF );
gl::stencilMask( 0x00 );

// Render your text like you normally would.

Finally, restore the OpenGL state to no longer use the mask:

// Restore stencil buffer.
ctx->popStencilMask();
ctx->popStencilOp();
ctx->popStencilFunc();
ctx->popBoolState( GL_STENCIL_TEST );

-Paul


#7

Oh wait, come to think of it… those ctx->pushStencil*() functions may not be part of Cinder at this time. If I remember correctly, I’ve added these to a separate branch of Cinder only. But you can use gl::stencil*() instead, the only difference is that you can not reliably restore the previous OpenGL state this way.


#8

Hi Paul,

Thanks for your very considered response! I will take some time to digest.

I originally used your TextRendering code years ago with cinder pre 0.9 (I don’t think there was Cinder-SDF at the time). Then cinder 0.9 came and it broke the sample, but I stumbled across the fact that the TextRendering sample was updated, allowing me to start playing with it again! Thanks for publishing code like that man :slight_smile:

I agree SDF text is cool. So much nicer when the camera zooms than bitmaps. My understanding is that your version is not as accurate than others, but faster? (have not tested, just going by what some dude said on a forum years ago (might have been yourself!), sorry don’t remember exactly where). I am ready to accept a loss in quality for performance, as the quality looks good to me :slight_smile:

Thanks for the link to Cinder-SDF. I will check this out further.

Now…just need to find a nice gui library that uses SDF… :slightly_smiling_face:

Cheers


#9

@laythea We’re currently working on incorporating SDFText into our scene graph https://github.com/bluecadet/Cinder-BluecadetViews FWIW. Our text block (https://github.com/bluecadet/Cinder-BluecadetText) currently uses GDI and supports some light inline styling and pseudo stylesheets. It’s been a slow burn transitioning from GDI to SDF since the practical paradigms are quite different, but let me know if you’re interested. We’re currently working on it every other day and starting to test it on projects.

Potion also has a good scene graph (https://github.com/Potion/Cinder-poScene) and text library (https://github.com/Potion/Cinder-Text/) that they’ve been working on, but I’m not sure if they’ve started combining the two.

Edit: @paul.houx While we have you in this thread, we’re actually encountering issues with the Cinder-SdfText block where trailing whitespaces keep on getting trimmed, which has made it really tricky to combine multiple text runs. Off the top of your head, do you have an idea where that might be happening? The only place I could find was https://github.com/paulhoux/Cinder-SDFText/blob/master/src/cinder/gl/SdfText.cpp#L1225-L1234, but removing that doesn’t seem to have any noticeable effect 🤷


#10

I can’t remember exactly how we do the text layout, it’s been a while since I’ve worked on the block. My guess would indeed have been to look at that particular code, as well as the lines following it. The block wasn’t designed to used mixed styles / text runs, so I guess that would require substantial refactoring.